Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions c_bridges/string-ops-bridge.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,66 @@
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

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];
Expand Down
1 change: 1 addition & 0 deletions src/codegen/infrastructure/llvm-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
32 changes: 5 additions & 27 deletions src/codegen/types/collections/string/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading