From 47daf49c77e8259ceb4ea807ecd7f26f79f60a37 Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Mon, 10 Nov 2025 21:57:29 -0500 Subject: [PATCH 1/2] Fix sign-extension of 64-bit literals When SUPPORT_64_BIT_LITERALS is not enabled, INTEGER nodes store the negation of the integer value. This is done to support the full range of 32-bit signed integers, since the positive value 2147483648 cannot be represented as a positive signed 32-bit integer. To get the actual value, it must be negated. If the negated value is -2147483648, negating it produces 2147483648, which overflows the 32-bit signed integer range (assuming the host platform uses 32-bit integers) and loops back to -2147483648. Additionally, for 64-bit targets, the value must be sign-extended to 64-bits, which is done by left shifting by 31. On platforms with 32-bit integers, the two's complement representation of 2147483648 is the same as -2147483648's two's complement, 0x80000000 in hex, and so everything works out. On platforms with larger integers, such as some shells, the value 2147483648 is representable as a positive integer, and so negating -2147483648 produces 2147483648, which produces an invalid sign-extended value when shifted. Sign-extension is only performed when targeting 64-bit platforms, where 2147483648 >> 31 evaluates to 1. --- exe.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exe.c b/exe.c index 9a1c06bd..cbe56bb0 100644 --- a/exe.c +++ b/exe.c @@ -132,8 +132,9 @@ void emit_i32_le(int n) { void emit_i64_le(int n) { emit_i32_le(n); - // Sign extend to 64 bits. Arithmetic shift by 31 gives -1 for negative numbers and 0 for positive numbers. - emit_i32_le(n >> 31); + // Sign extend to 64 bits. Because int is not guaranteed to be exactly 32 + // bits, we check the 32th bit (sign bit) and fill with 0xff... if set. + emit_i32_le(TERNARY(n & 0x80000000, -1, 0)); } #ifdef SUPPORT_64_BIT_LITERALS From 9f1fdab9548383d160323e592a3f239f1b296e8b Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Tue, 11 Nov 2025 01:10:27 -0500 Subject: [PATCH 2/2] WIP --- pnut.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pnut.c b/pnut.c index 9ce8ad39..ee41bbfa 100644 --- a/pnut.c +++ b/pnut.c @@ -1029,6 +1029,7 @@ void u64_mul_u32(int *x, int y) { int m3 = (m1 & 0xffff) + (m2 & 0xffff); /* 0 .. 0x1fffe */ int hi = xhi * yhi + I32_LOGICAL_RSHIFT_16(m1) + I32_LOGICAL_RSHIFT_16(m2) + I32_LOGICAL_RSHIFT_16(m3); /* 0 .. 0xfffffffe */ x[0] = ((m3 & 0xffff) << 16) + (lo & 0xffff); + // FIXME: Does that work with sizeof(int) > 4? x[1] = x[1] * y + hi; } @@ -1036,6 +1037,7 @@ void u64_mul_u32(int *x, int y) { void u64_add_u32(int *x, int y) { int lo = x[0] + y; // Carry (using signed integers) + // FIXME: Does that work with sizeof(int) > 4? x[1] += ((x[0] < 0) != (lo < 0)); x[0] = lo; } @@ -1046,7 +1048,7 @@ void u64_add_u32(int *x, int y) { // store it as a regular integer. The sign bit is used to distinguish between // large ints (positive) and regular ints (negative). void u64_to_obj(int *x) { - if (x[0] >= 0 && x[1] == 0) { // "small int" + if ((x[0] & 0x80000000) == 0 && x[1] == 0) { // "small int" val = -x[0]; } else { val = alloc_obj(2); @@ -1056,7 +1058,7 @@ void u64_to_obj(int *x) { } #define DIGIT_BYTE (val_32[0] % 256) -#define INIT_ACCUM_DIGIT() val_32[0] = 0; val_32[1] = 0; +#define INIT_ACCUM_DIGIT() val_32[0] = val_32[1] = 0; #else #define DIGIT_BYTE (-val % 256) #define INIT_ACCUM_DIGIT() val = 0; @@ -1075,17 +1077,29 @@ int accum_digit(int base) { if (digit >= base) { return 0; // character is not a digit in that base } else { - int limit = MININT / base; - if (base == 10 && if_macro_mask && ((val < limit) || ((val == limit) && (digit > limit * base - MININT)))) { + +#ifdef SUPPORT_64_BIT_LITERALS + int limit_hi = MININT / base; + int limit_lo = (MININT % base + base) % base; // make it positive + if (base == 10 && if_macro_mask + && ((val_32[1] < limit_hi) + || ((val_32[1] == limit_hi) && (val_32[0] < limit_lo)) + || ((val_32[1] == limit_hi) && (val_32[0] == limit_lo) && (digit > limit_lo)))) { fatal_error("literal integer overflow"); } -#ifdef SUPPORT_64_BIT_LITERALS u64_mul_u32(val_32, base); u64_add_u32(val_32, digit); #else + int limit = MININT / base; + if (base == 10 && if_macro_mask + && ((val < limit) || ((val == limit) && (digit > limit * base - MININT)))) { + fatal_error("literal integer overflow"); + } + val = val * base - digit; #endif + get_ch(); return 1; }