|
Important
|
Slice landed: decimal rendering. INT_MIN-safe: the magnitude of Seventh slice of the variable-string backend wall
( |
int_to_string was wired in resolve/typecheck/interp; only the wasm codegen
was absent (fell through gen_call’s dispatch → `UnboundVariable).
Interp oracle: string_of_int n.
-
neg = n < 0. -
m = neg ? n : -n(viaSelect) — the non-positive magnitude. ForINT_MIN,negis true andm = n(no negation), so the un-representable positive magnitude is never formed. -
Digit count
dc— a do-while loop dividing a copy ofmby 10 until 0. Because it runs at least once,n = 0yieldsdc = 1(the single '0') with no special case. -
len = dc + neg; allocate4 + len; storelen. -
Write digits backwards — a do-while loop from the last byte:
[wpos] = 48 - (m % 10)(i32rem_struncates toward zero, som % 10 ⇐ 0and48 - (m%10) = '0' + digit), thenm = m / 10,wpos--, untilm = 0. -
If
neg, write'-'(45) atdst + 4.
m / 10 (div_s) never traps: the divisor is 10 (not 0), and the only
trapping div_s case (INT_MIN / -1) never occurs. The leading-'-' write
uses an If whose else-branch is a single Nop (no empty-else).
The wasm string was read back from linear memory ([len][utf8]) and compared
to String(n); the interp oracle (string_of_int) was confirmed against the
real library API. Both agree across zero, single digit, multi-digit, negative,
powers of ten, INT_MAX, and INT_MIN:
| n | interp / wasm | note |
|---|---|---|
0 |
"0" |
do-while yields one '0' |
7 |
"7" |
single digit |
42 |
"42" |
|
-5 |
"-5" |
sign |
-123 |
"-123" |
|
100 |
"100" |
trailing zeros |
2147483647 |
"2147483647" |
INT_MAX (10 digits) |
-2147483648 |
"-2147483648" |
INT_MIN — negative-space extraction |
The packed fixture tests/codegen/string_int_to_string.affine returns
3485103 (three positional bytes + INT_MIN/INT_MAX content/length probes);
both backends produce it.
-
test/test_e2e.ml— neweval_string_fnhelper + group "E2E String-wall slice 7 (int_to_string)": five full-string interp-oracle cases (0, 42, -123, INT_MAX, INT_MIN). Runs underdune runtest. -
tests/codegen/string_int_to_string.affine
tests/codegen/test_string_int_to_string.mjs— executable wasm parity, run bytools/run_codegen_wasm_tests.sh(CI).
Full tools/run_codegen_wasm_tests.sh run: all codegen WASM tests pass
(slices 1-7 string harnesses), no sibling regressions.
Integer-to-text rendering — one of the most common string-building operations,
explicitly called out in the migration recipe as a "hand-ladder int rendering"
workaround — now lowers to wasm. This closes the clean lane: every
name-dispatched string builtin with a faithful trap-free wasm analogue is
lowered (string_length, string_char_code_at, char_to_int,
string_from_char_code, string_sub, to_lowercase, to_uppercase, trim,
string_find, int_to_string).
-
Concatenation (
` / `OpConcat`) and polymorphic `slice`* — the remaining high-value string ops, *blocked on a type-access decision*: `lib/codegen.ml` has no per-expression type information (`ExprBinary` is an untyped AST node; no type environment is threaded into codegen), and `already lowers to *list concatenation. Lowering them for strings requires either (a) threading typecheck’s inferred types into the AST / a side table consumed by codegen, or (b) adding a dedicatedstring_concatbuiltin (name-dispatched, sidesteps the operator overload) — a language-surface decision. Both are owner calls. -
float_to_string— needs the float story; stays host-side under the float-wall convention. -
string_get/int_to_char— deferred (raise on out-of-range; no trap-free wasm analogue without a declared-clamp convention).