The internal assembler routed every `movq`/`movd` to the integer mov encoder,
which reads both operands as general-purpose registers. So `movq %xmm0, %rax`
was emitted as `mov %rax,%rax` (48 89 C0) — register 0 in both fields — silently
discarding the XMM value.
The native backend emits `movq %xmm0, %rax` to box a Double's raw bits into an
array-of-const (the Format() argument path) and to spill float bit patterns, so
ANY float passed to Format() under the internal assembler (now the default
toolchain) arrived as garbage and printed 0.0000. A regression vs v0.11.0,
which used the external assembler. The e2e test harness assembles native via
cc, so only the internal-assembler suite exercises this path.
Fix: `movq`/`movd` with an XMM operand is an SSE move and is routed to a new
EncodeMovqXmm:
gp/mem -> xmm : 66 [REX.W] 0F 6E /r
xmm -> gp/mem : 66 [REX.W] 0F 7E /r
with the XMM operand as the ModRM.reg field and REX.W set for movq (64-bit),
clear for movd (32-bit).
Tests:
- cp.test.assembler.pas TAsmEncodingTests: byte-level encoding of
`movq %xmm0,%rax` (66 48 0F 7E C0, and asserts it is NOT 48 89 C0) and
`movq %rax,%xmm0` (66 48 0F 6E C0).
- TInternalAsmE2ETests.TestFormatFloatArg: Format('%.4f',[1.25]) through the
internal assembler prints 1.2500.
Verified: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK; full suite green
on both the QBE-built and native-built runners (3707 tests).