feat(runtime,backend): Float64 and Float32 bits() and ofBits(_) #358

Merged
argus merged 1 commit from loop/348-float-bits into main 2026-10-13 07:53:13 +00:00
Collaborator

Closes #348

What and why

Adds Float64.bits(): UInt64, Float64.ofBits(_): Float64, Float32.bits(): UInt32 and Float32.ofBits(_): Float32 to std/number. Each one is a memcpy runtime function (torb_bits_f64, torb_of_bits_f64, torb_bits_f32, torb_of_bits_f32). The C back end and the VM thunks call the same functions, so both give the same bits.

  • VM: a register holds a Float32 as the double it equals. A C cast between float and double quiets a signalling nan such as 0x7fa00000, so every float in a thunk now crosses a register through two new helpers in runtime/machine.c: torb_machine_float_of and torb_machine_word_of_float. These carry a nan by its sign and payload bits and convert every other value exactly as before. This touches the parameters of torb_compare_f32 and torb_show_f32 and the out-parameters of torb_parse_f32 and torb_convert_f64_f32_checked, without changing their results for any value that is not a nan.
  • Constants: a native call is never folded, so const x = Float64.ofBits(...) at the top level is the usual "has to be known at compile time" error. A new test in constants.test.trb pins this.
  • Seed rule: this PR only adds natives. Nothing in std/ or the compiler uses them yet (the seed rule of #35). Moving std/binary/src/number.trb onto them is the second commit, after a seed that knows them is published. docs/design/BINARY.md section 10 says so.
  • Generated files: runtime/include/torb_natives.h and runtime/machine_natives.c are regenerated. The four new symbols renumber the later thunks; after the rebase onto the Decimal natives the table is the union of both, in the manifest's sorted order.

Tests

  • runtime/tests/number_test.c: round trips of every special pattern, plus 1.0 → 0x3ff0000000000000 and 0x3f800000.
  • compiler/tests: the manifest entries are ready, the float thunks cross through the new helpers, and a bit cast is never constant-folded (the harness prelude has no std/number, so that test declares the two Float64 natives itself).
  • tests/conformance/float64-bits.trb and float32-bits.trb: -0.0, subnormals, the infinities, quiet, signalling and negative nans with payloads, carried through a parameter, a list and a reassigned variable, in both back ends. Their allocation budgets (.allocations: 550 and 358 blocks) are the counts of the first CI run.
  • Docs: doc comments in std/number, docs/standard-library/number.md with its skill copy, and BINARY.md section 10.

Bootstrap, gates and conformance run in CI only.

Closes #348 ## What and why Adds `Float64.bits(): UInt64`, `Float64.ofBits(_): Float64`, `Float32.bits(): UInt32` and `Float32.ofBits(_): Float32` to `std/number`. Each one is a `memcpy` runtime function (`torb_bits_f64`, `torb_of_bits_f64`, `torb_bits_f32`, `torb_of_bits_f32`). The C back end and the VM thunks call the same functions, so both give the same bits. - **VM:** a register holds a `Float32` as the `double` it equals. A C cast between `float` and `double` quiets a signalling nan such as `0x7fa00000`, so every `float` in a thunk now crosses a register through two new helpers in `runtime/machine.c`: `torb_machine_float_of` and `torb_machine_word_of_float`. These carry a nan by its sign and payload bits and convert every other value exactly as before. This touches the parameters of `torb_compare_f32` and `torb_show_f32` and the out-parameters of `torb_parse_f32` and `torb_convert_f64_f32_checked`, without changing their results for any value that is not a nan. - **Constants:** a native call is never folded, so `const x = Float64.ofBits(...)` at the top level is the usual "has to be known at compile time" error. A new test in `constants.test.trb` pins this. - **Seed rule:** this PR only adds natives. Nothing in `std/` or the compiler uses them yet (the seed rule of #35). Moving `std/binary/src/number.trb` onto them is the second commit, after a seed that knows them is published. `docs/design/BINARY.md` section 10 says so. - **Generated files:** `runtime/include/torb_natives.h` and `runtime/machine_natives.c` are regenerated. The four new symbols renumber the later thunks; after the rebase onto the `Decimal` natives the table is the union of both, in the manifest's sorted order. ## Tests - `runtime/tests/number_test.c`: round trips of every special pattern, plus `1.0` → `0x3ff0000000000000` and `0x3f800000`. - `compiler/tests`: the manifest entries are ready, the float thunks cross through the new helpers, and a bit cast is never constant-folded (the harness prelude has no `std/number`, so that test declares the two `Float64` natives itself). - `tests/conformance/float64-bits.trb` and `float32-bits.trb`: `-0.0`, subnormals, the infinities, quiet, signalling and negative nans with payloads, carried through a parameter, a list and a reassigned variable, in both back ends. Their allocation budgets (`.allocations`: 550 and 358 blocks) are the counts of the first CI run. - Docs: doc comments in `std/number`, `docs/standard-library/number.md` with its skill copy, and `BINARY.md` section 10. Bootstrap, gates and conformance run in CI only.
feat(runtime,backend): Float64 and Float32 bits() and ofBits(_)
Some checks failed
ci / commit messages (pull_request) Successful in 11s
ci / what the change touches (pull_request) Successful in 11s
pdisc / pdisc (pull_request) Failing after 13s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 16s
ci / bootstrap (linux-x64) (pull_request) Successful in 5m31s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Failing after 4m31s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m58s
ci / every host emits the same C (pull_request) Successful in 4m23s
ci / tier B (linux-x64) (pull_request) Failing after 11m22s
ci / pack the seed (pull_request) Has been skipped
ci / archives (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m46s
ci / gates (pull_request) Failing after 0s
f6acdc5377
`Float64.bits(): UInt64`, `Float64.ofBits(_): Float64`, `Float32.bits(): UInt32`
and `Float32.ofBits(_): Float32` are runtime functions (`torb_bits_f64`,
`torb_of_bits_f64`, `torb_bits_f32`, `torb_of_bits_f32`) that copy the IEEE-754
pattern with `memcpy`, so the C back end and the VM call the same code and
every pattern keeps its bits: -0.0, subnormals, the infinities and a nan with a
payload.

A VM register holds a Float32 as the double it equals, and a C cast between
float and double quiets a signalling nan (0x7fa00000). The thunks therefore
cross a float through `torb_machine_float_of` and `torb_machine_word_of_float`,
which carry a nan by its sign and payload bits and convert every other value
exactly as before.

A call of either is never folded into a top-level constant; a compiler test
pins that. New natives only: nothing in std or the compiler uses them yet
(seed rule of #35). Moving std/binary onto them follows a seed that knows them.

float64-bits.trb and float32-bits.trb are new conformance programs; their
allocation budgets come from CI.

Refs #348
orion requested review from argus 2026-10-13 04:02:47 +00:00
orion force-pushed loop/348-float-bits from f6acdc5377
Some checks failed
ci / commit messages (pull_request) Successful in 11s
ci / what the change touches (pull_request) Successful in 11s
pdisc / pdisc (pull_request) Failing after 13s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 16s
ci / bootstrap (linux-x64) (pull_request) Successful in 5m31s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Failing after 4m31s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m58s
ci / every host emits the same C (pull_request) Successful in 4m23s
ci / tier B (linux-x64) (pull_request) Failing after 11m22s
ci / pack the seed (pull_request) Has been skipped
ci / archives (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m46s
ci / gates (pull_request) Failing after 0s
to f4536c7a28
All checks were successful
ci / commit messages (pull_request) Successful in 6s
ci / what the change touches (pull_request) Successful in 8s
pdisc / pdisc (pull_request) Successful in 12s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Successful in 2m3s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 17s
ci / tier A (linux-x64) (pull_request) Successful in 4m33s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 2m26s
ci / every host emits the same C (pull_request) Successful in 4m36s
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m56s
ci / tier B (linux-x64) (pull_request) Successful in 12m2s
ci / pack the seed (pull_request) Has been skipped
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
2026-10-13 07:09:59 +00:00
Compare
argus approved these changes 2026-10-13 07:53:06 +00:00
argus left a comment

Approved at f4536c7. Checklist:

  • Tests: ok. number_test.c round trips, manifest and thunk-text tests, the constant-folding test, and conformance programs for both widths. CI is green on this head (bootstrap, tier A/B, gates, pdisc).
  • Correctness: ok. The four natives are plain memcpy. The torb_machine_float_of / torb_machine_word_of_float mapping puts the 23-bit float payload at the top of the 52 bits of the double, and the sign and non-nan values convert as before.
  • Scope: ok. Matches the acceptance criteria of #348. Nothing in std/ or the compiler uses the natives yet, so the seed rule holds.
  • Security/data: ok. No new permissions, dependencies or stored formats.
  • Docs: ok. Doc comments, number.md with its skill copy, BINARY.md section 10 and the conformance README.

Non-blocking:

  • torb_machine_float_of treats any double nan whose top 23 payload bits are zero (for example 0x7ff0000000000001) as 0x7f800000, which is an infinity. A float register only gets nans through torb_machine_word_of_float, whose low 29 bits are always zero, so this cannot happen today. Forcing the quiet bit or a nonzero payload in that branch would make it robust against other writers.
Approved at f4536c7. Checklist: - **Tests:** ok. `number_test.c` round trips, manifest and thunk-text tests, the constant-folding test, and conformance programs for both widths. CI is green on this head (bootstrap, tier A/B, gates, pdisc). - **Correctness:** ok. The four natives are plain `memcpy`. The `torb_machine_float_of` / `torb_machine_word_of_float` mapping puts the 23-bit float payload at the top of the 52 bits of the double, and the sign and non-nan values convert as before. - **Scope:** ok. Matches the acceptance criteria of #348. Nothing in `std/` or the compiler uses the natives yet, so the seed rule holds. - **Security/data:** ok. No new permissions, dependencies or stored formats. - **Docs:** ok. Doc comments, `number.md` with its skill copy, `BINARY.md` section 10 and the conformance README. Non-blocking: - `torb_machine_float_of` treats any double nan whose top 23 payload bits are zero (for example `0x7ff0000000000001`) as `0x7f800000`, which is an infinity. A float register only gets nans through `torb_machine_word_of_float`, whose low 29 bits are always zero, so this cannot happen today. Forcing the quiet bit or a nonzero payload in that branch would make it robust against other writers.
argus merged commit d37b931ef2 into main 2026-10-13 07:53:13 +00:00
argus deleted branch loop/348-float-bits 2026-10-13 07:53:14 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!358
No description provided.