diff --git a/c_bridges/string-ops-bridge.c b/c_bridges/string-ops-bridge.c index da2dbff8..d97502df 100644 --- a/c_bridges/string-ops-bridge.c +++ b/c_bridges/string-ops-bridge.c @@ -1,10 +1,66 @@ #include #include #include +#include +#include extern void *cs_arena_alloc(size_t size); extern void *GC_malloc(size_t size); +// Fast number-to-string with integer fast path. +// Matches JS semantics for integer-valued doubles (no trailing ".0"), +// and falls back to snprintf("%.15g") for non-integers and specials. +// Returns arena-allocated null-terminated string. +char *cs_num_to_str(double val) { + // Fast path: integer-valued doubles in [-2^53, 2^53]. The %.15g format + // produces pure integer text for these, so we can itoa directly. + // Also handles -0.0 correctly (prints "0", matching JS stringification). + if (val == 0.0) { + char *out = (char *)cs_arena_alloc(2); + out[0] = '0'; + out[1] = '\0'; + return out; + } + // Reject NaN/Infinity and non-integers via a single check. + // Casting NaN/Inf to int64 is UB, so gate on isfinite first. + if (__builtin_expect(isfinite(val), 1)) { + double truncated = (double)(int64_t)val; + if (truncated == val && val >= -9007199254740992.0 && val <= 9007199254740992.0) { + int64_t n = (int64_t)val; + // Up to 20 digits + sign + null. + char buf[24]; + int pos = 23; + buf[pos--] = '\0'; + int negative = 0; + uint64_t u; + if (n < 0) { + negative = 1; + u = (uint64_t)(-(n + 1)) + 1; // safe for INT64_MIN + } else { + u = (uint64_t)n; + } + do { + buf[pos--] = (char)('0' + (u % 10)); + u /= 10; + } while (u != 0); + if (negative) buf[pos--] = '-'; + size_t start = (size_t)(pos + 1); + size_t len = 23 - start; + char *out = (char *)cs_arena_alloc(len + 1); + memcpy(out, buf + start, len + 1); + return out; + } + } + // Fallback: non-integer, NaN, or Infinity. + char tmp[48]; + int n = snprintf(tmp, sizeof(tmp), "%.15g", val); + if (n < 0) n = 0; + if (n >= (int)sizeof(tmp)) n = (int)sizeof(tmp) - 1; + char *out = (char *)cs_arena_alloc((size_t)n + 1); + memcpy(out, tmp, (size_t)n + 1); + return out; +} + void cs_to_upper(const char *src, char *dst, size_t len) { for (size_t i = 0; i < len; i++) { unsigned char c = (unsigned char)src[i]; diff --git a/src/codegen/infrastructure/llvm-declarations.ts b/src/codegen/infrastructure/llvm-declarations.ts index 8986b829..9af485a8 100644 --- a/src/codegen/infrastructure/llvm-declarations.ts +++ b/src/codegen/infrastructure/llvm-declarations.ts @@ -237,6 +237,7 @@ export function getLLVMDeclarations(config?: DeclConfig): string { ir += "declare %StringArray* @cs_str_array_to_upper(%StringArray*)\n"; ir += "declare %StringArray* @cs_str_array_to_lower(%StringArray*)\n"; ir += "declare void @cs_str_cache_invalidate()\n"; + ir += "declare i8* @cs_num_to_str(double)\n"; ir += "\n"; ir += "declare i32 @system(i8*)\n"; diff --git a/src/codegen/types/collections/string/constants.ts b/src/codegen/types/collections/string/constants.ts index 9d685fb5..73427ae4 100644 --- a/src/codegen/types/collections/string/constants.ts +++ b/src/codegen/types/collections/string/constants.ts @@ -89,34 +89,12 @@ export function createStringConstant(ctx: IGeneratorContext, value: string): str } export function convertNumberToString(ctx: IGeneratorContext, numValue: string): string { - const bufferSize = ctx.nextAllocaReg("numstr_buf"); - ctx.emit(`${bufferSize} = alloca [48 x i8], align 1`); - - const bufferPtr = ctx.nextTemp(); - ctx.emit( - `${bufferPtr} = getelementptr inbounds [48 x i8], [48 x i8]* ${bufferSize}, i64 0, i64 0`, - ); - - const formatStr = createStringConstant(ctx, "%.15g"); - + // Fast path via string-ops-bridge: integer-valued doubles (the overwhelming + // common case) are formatted with a direct itoa instead of snprintf("%.15g"), + // which is dominated by locale lookups and Balloc/d2b machinery. Non-integers + // fall back to snprintf inside the bridge. const dblValue = ctx.ensureDouble(numValue); - const snprintfResult = ctx.nextTemp(); - ctx.emit( - `${snprintfResult} = call i32 (i8*, i64, i8*, ...) @snprintf(i8* ${bufferPtr}, i64 48, i8* ${formatStr}, double ${dblValue})`, - ); - - const strLen = ctx.nextTemp(); - ctx.emit(`${strLen} = call i64 @strlen(i8* ${bufferPtr})`); - - const heapSize = ctx.nextTemp(); - ctx.emit(`${heapSize} = add i64 ${strLen}, 1`); - - const heapPtr = ctx.nextTemp(); - ctx.emit(`${heapPtr} = call i8* @cs_arena_alloc(i64 ${heapSize})`); - - const copyResult = ctx.nextTemp(); - ctx.emit(`${copyResult} = call i8* @strcpy(i8* ${heapPtr}, i8* ${bufferPtr})`); - + const heapPtr = ctx.emitCall("i8*", "@cs_num_to_str", `double ${dblValue}`); ctx.setVariableType(heapPtr, "i8*"); return heapPtr; }