luvcraft/physics-simd.lisp
Define the PHYSICS-* kernel methods for family using package's f32.4
operations. blend is :bit-select where the package has F32.4-BIT-SELECT
and :and-or where SSE2's U32.4 comparison masks select the float lane bits.
SSE2 comparisons produce U32.4 masks, while SBCL's SSE F32.4! cast cannot reinterpret a P128 value. Do the bit selection in the integer view, then retag its four lanes as floats. In a compiled kernel the casts into U32.4 are register moves; only this final retag is expanded lane by lane.
sb-simd 2.6.7's NEON F32.4-SQRT is mis-encoded and
raises SIGILL, so the two square roots a lane group
needs go through the scalar unit lane by lane. The
result is what the vector instruction would give.
The relative velocity at the contact point of each side.
Apply the impulse P to both sides' linear and angular state.
The delta lanes go four at a time; the orientations, whose
normalization the eye alone consumes, stay scalar.
Rolling resistance.
Where nothing is over, the divisor is
pushed to one and the quotient discarded.
A resistance of zero keeps its impulse at zero.
Friction.
Only a real hit bounces: it pushed, and it
arrived fast enough, and it has bounce in it.
The families this image can run. NEON is the whole 128-bit menu on arm64; SSE is the same width on x86-64. Availability is a runtime property (#C3F7XM): the methods are defined whenever the package exists, and the default family is chosen at load time.
The kernel families this image can run, fastest first.
(family package &key blend)Define the PHYSICS-* kernel methods for FAMILY using PACKAGE's f32.4 operations. BLEND is :bit-select where the package has F32.4-BIT-SELECT and :and-or where SSE2's U32.4 comparison masks select the float lane bits.
The componentwise square root of a raw value.
(kernels awake h)Apply gravity and damping to the awake bodies over H.
((bindings buffer buffer-type) &body body)Borrow BUFFER-TYPE's active extent, row declaration, and raw lane arrays. BINDINGS is (LENGTH ROW-DECLARATION (ARRAY LANE-NAME) ...). The buffer is evaluated once, and every array receives its precise specialized array type. This is the checked aggregate boundary for closed scalar or SIMD kernels; the kernel…
Multiplication and scalar scaling.
Gravitational acceleration, cells per second squared, along Y.
Division of two represented quantities.
Addition over compatible quantities.
(kernels awake h)Advance the awake bodies' deltas and orientations by H.
(kernels constraints awake starts)Apply the carried impulses of every constraint, colour by colour. STARTS gives each colour's first row, with one more entry than colours.
((start end starts) &body body)Subtraction or unary negation.
((constraints awake) &body body)Test whether one compatible scalar is less than another.
(constraints awake start end)(kernels constraints awake starts inv-h use-bias-p elapsed push-max)One Gauss-Seidel iteration over every colour in turn: normal impulses with soft or speculative bias when USE-BIAS-P, and without bias plus friction and rolling resistance when not. ELAPSED is how far into the step the substep is, for a kinematic other side's motion.
(constraints awake start end inv-h use-bias-p elapsed push-max)(kernels constraints awake starts threshold)Bounce the constraints that hit hard enough, colour by colour.
(constraints awake start end threshold)Logical conjunction of tests and raw truth values.
The kernel family MAKE-PHYSICS-WORLD picks by default; PHYSICS-SIMD.LISP sets it to a native family when one is available.
The four-wide contact kernels: what the colouring bought (#G3W7KD).
PHYSICS.LISP solves one contact at a time. Within a colour no two contacts share an awake body, so four contacts of a colour can be gathered into f32.4 lanes, solved together with the same arithmetic in the same order, and scattered back without any lane's write landing on another lane's read. That is exactly Box3D's arrangement (#T3C8FV): the constraint columns are already the struct-of-arrays the lanes want, and only the body state needs gathering.
The kernels here are generated once per instruction family from one template, so the NEON family on arm64 and the SSE family on x86-64 are the same code lowered through sb-simd's differently named packages; a scalar family always exists as the reference (#E8M3JC). A wide kernel runs the lane groups it can and hands its tail of fewer than four contacts back to the scalar kernel. Branches become blends: where the scalar kernel skips work, the wide kernel does the work into a lane it then ignores, which is why the arithmetic never divides by a value it has not first pushed away from zero.
The claim these kernels make is testable and tested: a world stepped with the wide family and the same world stepped with the scalar family reach bit-identical state (PHYSICS-TESTS.LISP). Neither path uses a fused multiply-add or an approximate reciprocal, for that reason. #7PAQ3M