From f32a6da8f644cf1a1acbeffc4ccf028a0e377954 Mon Sep 17 00:00:00 2001 From: Spotandjake Date: Thu, 30 Apr 2026 15:05:28 -0400 Subject: [PATCH] feat: Cleanup to shrink smallest program size This is a rather small pr, I was looking through the binary of our smallest program and noticed a few things that were being left behind. Not very important but we can save a few bytes by removing the unnecessary function wrapping our exception setup, this function was required when exception handling was unsafe however it's now completely safe so there is no reason to map it. The bigger change is I noticed that `bigIntToString` was doing some weird things, firstly the `_SIZES` array wasn't actually ever being used however it was causing the function to require a closure which was being setup in every program and adding a rather substantial amount of size. Secondly because we had a helper function that used`_DIGITS` inside of the toString method this created a closure and because we were not lazily initializing digits this was adding quite a few bytes to the program, with a few simple changes this cuts program size and I tracked length along with the result buffer when stringifying which saves an iteration over the result buffer, and the corresponding helper. --- compiler/test/suites/basic_functionality.re | 2 +- stdlib/pervasives.gr | 28 +++-- stdlib/runtime/bigint.gr | 108 ++++++-------------- 3 files changed, 44 insertions(+), 94 deletions(-) diff --git a/compiler/test/suites/basic_functionality.re b/compiler/test/suites/basic_functionality.re index 4675ea523c..d48dac8e19 100644 --- a/compiler/test/suites/basic_functionality.re +++ b/compiler/test/suites/basic_functionality.re @@ -485,6 +485,6 @@ describe("basic functionality", ({test, testSkip}) => { ~config_fn=smallestFileConfig, "smallest_grain_program", "", - 329, + 284, ); }); diff --git a/stdlib/pervasives.gr b/stdlib/pervasives.gr index 6e28923824..defa64d262 100644 --- a/stdlib/pervasives.gr +++ b/stdlib/pervasives.gr @@ -230,21 +230,17 @@ provide primitive unbox = "@unbox" // Setup exception printing primitive elideTypeInfo = "@meta.elide_type_info" -@unsafe -let setupExceptions = () => { - Exception.registerPrinter(e => { - match (e) { - Failure(msg) => Some("Failure: " ++ msg), - InvalidArgument(msg) => Some("Invalid argument: " ++ msg), - _ => None, - } - }) - - // If type information is elided, remove dependency on toString as - // it will have no effect on exceptions - if (!elideTypeInfo) { - Exception.registerBasePrinter(e => toString(e)) + +Exception.registerPrinter(e => { + match (e) { + Failure(msg) => Some("Failure: " ++ msg), + InvalidArgument(msg) => Some("Invalid argument: " ++ msg), + _ => None, } -} +}) -setupExceptions() +// If type information is elided, remove dependency on toString as +// it will have no effect on exceptions +if (!elideTypeInfo) { + Exception.registerBasePrinter(e => toString(e)) +} diff --git a/stdlib/runtime/bigint.gr b/stdlib/runtime/bigint.gr index 22bc99663b..9bdb2233f4 100644 --- a/stdlib/runtime/bigint.gr +++ b/stdlib/runtime/bigint.gr @@ -691,53 +691,20 @@ let countTrailingZeroBits = num => { result } -let _DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz" -// maximum number of digits that can fully fit a uint64 (for each valid base): -let _SIZES = [> - 0, - 0, - 64, // 2 - 40, // 3 - 32, // 4 - 27, // 5 - 24, // 6 - 22, // 7 - 21, // 8 - 20, // 9 - 19, // 10 - 18, // 11 - 17, // 12 - 17, // 13 - 16, // 14 - 16, // 15 - 16, // 16 - 15, // 17 - 15, // 18 - 15, // 19 - 14, // 20 - 14, // - 14, // - 14, // 23 - 13, // 24 - 13, // - 13, // - 13, // - 13, // - 13, // - 13, // - 12, - 12, - 12, - 12, - 12, - 12, -] +@unsafe +let mut _DIGITS = WasmRef.fromGrain(void) + +@unsafe +let getDigit = n => { + if (WasmRef.isRefI31(_DIGITS)) { + _DIGITS = WasmRef.fromGrain("0123456789abcdefghijklmnopqrstuvwxyz") + } + DS.tagChar(WasmArrayRef.getI8U(DS.getStringArrayRef(_DIGITS), n)) +} @unsafe provide let bigIntToString = (num, base) => { use WasmI32.{ (+), (<), (>) } - let getDigit = n => - WasmArrayRef.getI8U(DS.getStringArrayRef(WasmRef.fromGrain(_DIGITS)), n) if (base < 2n || base > 32n) { throw Exception.InvalidArgument("toString base must be in range [2,32]") } @@ -745,16 +712,14 @@ provide let bigIntToString = (num, base) => { if (eqz(num)) { "0" } else { - let size = DS.untagSimpleNumber(_SIZES[DS.tagSimpleNumber(base)]) use WasmI32.{ (==), (-) as subWasmI32 } let mut result = [] + let mut length = 0n if (base == 2n || base == 4n || base == 8n || base == 16n || base == 32n) { // if base is a power of two, use optimized path let bits = WasmI64.extendI32U(WasmI32.ctz(base)) let mask = (1N << bits) - 1N let numLimbs = getSize(num) - let totalBits = 64N * WasmI64.extendI32U(numLimbs) - - WasmI64.clz(getLimb(num, subWasmI32(numLimbs, 1n))) let mut acc = 0N let mut accBits = 0N for (let mut i = 0n; i < numLimbs; i += 1n) { @@ -763,10 +728,9 @@ provide let bigIntToString = (num, base) => { acc = acc | limb << accBits accBits += 64N while (accBits >= bits) { - result = [ - DS.tagChar(getDigit(WasmI32.wrapI64(acc & mask))), - ...result - ] + use WasmI32.{ (+) } + result = [getDigit(WasmI32.wrapI64(acc & mask)), ...result] + length += 1n acc = acc >>> bits if (accBits > 64N) { acc = limb >>> (64N - (accBits - bits)) @@ -775,13 +739,13 @@ provide let bigIntToString = (num, base) => { } } if (acc > 0N) { - result = [DS.tagChar(getDigit(WasmI32.wrapI64(acc))), ...result] + result = [getDigit(WasmI32.wrapI64(acc)), ...result] + length += 1n } } else { let base = WasmI64.extendI32U(base) let d = base let mut tmp = clone(num) - setFlag(tmp, _IS_NEGATIVE, 0n) while (!eqz(tmp)) { use WasmI32.{ (-), (<<), (>=) } let tmpCopy = tmp @@ -801,45 +765,35 @@ provide let bigIntToString = (num, base) => { } } tmp = trimNumber(tmp) - result = [ - DS.tagChar(getDigit(WasmI32.wrapI64(WasmI64.remU(c, base)))), - ...result - ] + result = [getDigit(WasmI32.wrapI64(WasmI64.remU(c, base))), ...result] + length += 1n } } - while (match (result) { - [c, ...tl] when DS.untagChar(c) == DS.untagChar('0') => true, - _ => false, - }) { + while (true) { match (result) { - [c, ...tl] => result = tl, - _ => void, // <- impossible + [c, ...tl] when DS.untagChar(c) == DS.untagChar('0') => { + use WasmI32.{ (-) } + result = tl + length -= 1n + }, + _ => break, } } if (flagIsSet(num, _IS_NEGATIVE)) { result = ['-', ...result] + length += 1n } - @unsafe - let rec computeLength = (lst, acc) => { - match (lst) { - [] => acc, - [_, ...tl] => computeLength(tl, acc + 1n), - } - } - let length = computeLength(result, 0n) let ret = DS.allocateString(length) let retArray = DS.getStringArrayRef(ret) - @unsafe - let rec populateString = (lst, idx, str) => { - match (lst) { - [] => void, + for (let mut i = 0n; i < length; i += 1n) { + match (result) { [hd, ...tl] => { - WasmArrayRef.setI8(str, idx, DS.untagChar(hd)) - populateString(tl, idx + 1n, str) + WasmArrayRef.setI8(retArray, i, DS.untagChar(hd)) + result = tl }, + [] => break, // <- impossible as length == result.length } } - populateString(result, 0n, retArray) WasmRef.toGrain(ret): String } }