c: byte semantics — string-ref unsigned cast, pack_u64_slot bignum sentinel
Two C-tier defects surfaced when running foxhop's ecdsa Phase B emit through the lumbda C interpreter. Both produced wrong bytes in the generated QECCOPS1 binary; both Python tier handled correctly. 1. string-ref on a byte >= 0x80 returned a char with codepoint -1. The store was a `char` array (signed on x86_64); `s->data[idx]` sign-extends 0xFF into a negative int before VAL_CHAR wraps it. Round-tripping (char->integer (string-ref s 0)) for a 0xFF byte gave -1 instead of 255. The Scheme-level (u64-le n) packer uses (make-string 1 (integer->char (modulo v 256))) for each byte and reads them back; sign-extension corrupted the high-bit bytes. Cast through `unsigned char` in bi_string_ref. 2. pack_u64_slot treated bignum slots as raw fixnums. ecdsa's emit-stream binds (no-slot *no-slot*) where *no-slot* is 18446744073709551615 (u64 max). On Python tier that's a regular big-int. C tier carries it as a bignum NaN-box slot. The packer knew about VAL_FALSE → 0xFFFF... but called as_int(v) on bignums, reading the NaN-box payload bits (pointer-to-Bignum) and writing that pointer as the field's 64-bit LE value. Add an IS_BIGNUM branch that extracts the low 64 magnitude bits directly. Cross-tier emit-stream code stays unchanged. Tests: 88/88 c-test, 4/4 regression-named-let-leak, 205/205 functional. DIALOG_GCD smoke at p=11 n+1=5 now byte-identical to Python tier (1,941,816 bytes match exactly).
This commit is contained in:
parent
269d3be756
commit
7b6643d1fc
1 changed files with 13 additions and 1 deletions
14
c/builtins.c
14
c/builtins.c
|
|
@ -748,7 +748,12 @@ static Value bi_string_ref(Value *a, int n, Env *e) {
|
|||
int idx = (int)as_number_int(a[1]);
|
||||
ULString *s = AS_STRING(a[0]);
|
||||
if (idx < 0 || idx >= (int)s->len) lisp_error("string-ref: index out of range");
|
||||
return VAL_CHAR(s->data[idx]);
|
||||
/* Cast through unsigned char so bytes 0x80..0xFF map to codepoints
|
||||
* 128..255 rather than sign-extending to negative ints — emit-stream
|
||||
* stores binary op bytes through (make-string 1 (integer->char b))
|
||||
* + (string-ref s 0); without this, char->integer of 0xFF returned
|
||||
* -1, so u64-le's serialization produced a sentinel-shaped giant. */
|
||||
return VAL_CHAR((unsigned char)s->data[idx]);
|
||||
}
|
||||
|
||||
static Value bi_substring(Value *a, int n, Env *e) {
|
||||
|
|
@ -1595,6 +1600,13 @@ static void pack_u64_slot(char *buf, Value v) {
|
|||
uint64_t u;
|
||||
if (v == VAL_FALSE) {
|
||||
u = 0xFFFFFFFFFFFFFFFFULL;
|
||||
} else if (IS_BIGNUM(v)) {
|
||||
/* Bignum sentinel (e.g. *no-slot* = 2^64-1) — extract low 64
|
||||
* bits of magnitude. Python tier passes the literal big-int so
|
||||
* cross-tier emit-stream code stays unchanged. */
|
||||
Bignum *b = AS_BIGNUM(v);
|
||||
u = b->n_limbs > 0 ? b->limbs[0] : 0;
|
||||
if (b->sign < 0) u = ~u + 1;
|
||||
} else {
|
||||
u = (uint64_t)as_int(v);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue