← board

Single (32-bit float) first-class on the internal-call ABI

Problem

Single (tySingle, 4-byte IEEE-754) was not first-class on the INTERNAL (PXX-ABI) call path. Passing a Single value to a Single parameter, or returning a Single, produced 0.00. Only the EXTERNAL C-call path narrowed/widened at the tySingle boundary; the internal path carried floats as DOUBLE bits and never narrowed at the param/return boundary. Subsumes feature-double-to-single-narrowing.

Root cause (per target)

The IR float value model carries floats as DOUBLE bits in the GPR (rax / x0 / eax:edx / r0:r1). At the Single boundary the bits must be narrowed (cvtsd2ss / fcvt s,d / vcvt.f32.f64) so a 4-byte slot holds true single bits, and widened back on load. The gaps were NOT uniform:

Fix

  1. x86-64 prologue (parser.inc, both the <=6-register and >6-stack spill loops): for a by-value tySingle param, movq xmm0,<reg> / cvtsd2ss / movss [rbp+off],xmm0 instead of the raw integer store.
  2. aarch64 prologue (parser.inc, i<8 branch): fmov d0,x[i] / fcvt s0,d0 / str s0,[x8].
  3. i386 caller (ir_codegen386.inc): add cvtsd2ss xmm0,xmm0 before the movss [esp],xmm0 Single-arg push.
  4. Overload clause (symtab.inc TypesCompatible): a float formal accepts any float actual (widen/narrow) or an ordinal actual (int->float), compatible-match only (after exact match). Added LAST, after the ABI, so ScaleS(1.5,3) no longer silently compiles to 0.00.

Acceptance / gates

Notes / landmines

Resolved-in: 26e8da2 (finalizing commit)