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
38 changes: 33 additions & 5 deletions src/chunk.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ void chunk_emit_u16(EigsChunk *chunk, uint16_t val, int line) {
chunk_emit(chunk, (uint8_t)((val >> 8) & 0xFF), line);
}

/* #630: little-endian 32-bit operand — currently only OP_LINE, whose line
* number legitimately exceeds 65535 in generated programs. Read by read_u32. */
void chunk_emit_u32(EigsChunk *chunk, uint32_t val, int line) {
chunk_emit(chunk, (uint8_t)(val & 0xFF), line);
chunk_emit(chunk, (uint8_t)((val >> 8) & 0xFF), line);
chunk_emit(chunk, (uint8_t)((val >> 16) & 0xFF), line);
chunk_emit(chunk, (uint8_t)((val >> 24) & 0xFF), line);
}

/* Emit a jump instruction with a placeholder 16-bit offset.
* Returns the offset of the placeholder for later patching. */
int chunk_emit_jump(EigsChunk *chunk, uint8_t op, int line) {
Expand Down Expand Up @@ -369,12 +378,13 @@ static int op_has_u16(uint8_t op) {
case OP_TRY_BEGIN: case OP_LOOP_STALL_CHECK: case OP_LOOP_CAP_CHECK:
case OP_OBSERVE_ASSIGN: case OP_OBSERVE_ASSIGN_LOCAL:
case OP_IMPORT: case OP_MATCH:
case OP_LINE:
case OP_REPORT_VALUE_SLOT: case OP_REPORT_VALUE_NAME:
case OP_TRAJECTORY_SLOT: case OP_TRAJECTORY_NAME:
return 1;
case OP_INTERROGATE: case OP_PREDICATE:
return 1; /* kind:8 but padded to 16 for uniformity */
/* OP_LINE has a 32-bit operand (#630) — handled separately by the
* disassembler and verifier, never through the u16 path. */
default:
return 0;
}
Expand All @@ -389,7 +399,15 @@ void chunk_disassemble(EigsChunk *chunk, const char *label) {
uint8_t op = chunk->code[i];
fprintf(stderr, "%04d [L%d] %-20s", i, line, op_name(op));
i++;
if (op_has_u16(op) && i + 1 < chunk->code_len) {
if (op == OP_LINE && i + 3 < chunk->code_len) {
/* #630: 32-bit operand. */
uint32_t arg = (uint32_t)chunk->code[i] |
((uint32_t)chunk->code[i + 1] << 8) |
((uint32_t)chunk->code[i + 2] << 16) |
((uint32_t)chunk->code[i + 3] << 24);
fprintf(stderr, " %u", arg);
i += 4;
} else if (op_has_u16(op) && i + 1 < chunk->code_len) {
uint16_t arg = chunk->code[i] | (chunk->code[i + 1] << 8);
fprintf(stderr, " %d", arg);
if (op == OP_CONST && arg < (uint16_t)chunk->const_count) {
Expand Down Expand Up @@ -462,8 +480,10 @@ static int op_verify_operands(uint8_t op, VerifyRole roles[3]) {
case OP_REPORT_SLOT: case OP_OBSERVE_VALUE_SLOT:
case OP_REPORT_VALUE_SLOT:
case OP_INTERROGATE: case OP_PREDICATE:
case OP_MATCH: case OP_LINE: case OP_DESTRUCTURE_UNPACK:
case OP_MATCH: case OP_DESTRUCTURE_UNPACK:
roles[0] = VR_RAW; return 1;
/* OP_LINE's operand is 32-bit (#630); chunk_verify walks it specially,
* so it never reaches this per-operand (u16-strided) role machinery. */
case OP_LOCAL_DOT_GET: case OP_LOCAL_DOT_SET:
roles[0] = VR_RAW; roles[1] = VR_NAME; return 2; /* slot, name */
case OP_LOCAL_IDX_GET:
Expand Down Expand Up @@ -498,6 +518,14 @@ int chunk_verify(EigsChunk *chunk) {
uint8_t op = code[i];
if (op >= OP_COUNT) { ok = 0; break; }
is_start[i] = 1;
/* #630: OP_LINE has a single 32-bit operand — outside the u16-strided
* role machinery below. No index to validate; just skip 4 bytes. */
if (op == OP_LINE) {
int end = i + 1 + 4;
if (end > n) { ok = 0; break; }
i = end;
continue;
}
VerifyRole roles[3];
int nops = op_verify_operands(op, roles);
int end = i + 1 + 2 * nops;
Expand Down Expand Up @@ -558,8 +586,8 @@ void chunk_scan_leaf_accessor(EigsChunk *c) {
uint8_t op = *ip++;
switch (op) {
case OP_LINE:
if (ip + 2 > end) return;
ip += 2;
if (ip + 4 > end) return; /* #630: 32-bit operand */
ip += 4;
break;
case OP_CONST: {
if (ip + 2 > end) return;
Expand Down
9 changes: 8 additions & 1 deletion src/compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,13 @@ static void emit_op_u16(Compiler *c, uint8_t op, uint16_t arg, int line) {
adjust_stack(c, op_stack_effect(op));
}

/* #630: 32-bit operand form (OP_LINE only). */
static void emit_op_u32(Compiler *c, uint8_t op, uint32_t arg, int line) {
chunk_emit(c->chunk, op, line);
chunk_emit_u32(c->chunk, arg, line);
adjust_stack(c, op_stack_effect(op));
}

static void emit_op_u16_u16(Compiler *c, uint8_t op, uint16_t arg1, uint16_t arg2, int line) {
chunk_emit(c->chunk, op, line);
chunk_emit_u16(c->chunk, arg1, line);
Expand Down Expand Up @@ -442,7 +449,7 @@ static int capture_loop_start(Compiler *c) {
* basic-block boundary so we never *skip* a stamp the runtime needs. */
static void emit_line(Compiler *c, int line) {
if (c->last_line == line) return;
emit_op_u16(c, OP_LINE, (uint16_t)line, line);
emit_op_u32(c, OP_LINE, (uint32_t)line, line); /* #630: 32-bit — was (uint16_t), wrapped past line 65535 */
c->last_line = line;
}

Expand Down
14 changes: 9 additions & 5 deletions src/jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,9 @@ static int jit_supported_prefix(const struct EigsChunk *chunk,
* call, no bail, no sp/env interaction. */
i += 1; ops++; non_line_ops++;
} else if (op == OP_LINE) {
if (i + 3 > chunk->code_len) { *stop_op = op; *stop_offset = i; break; }
i += 3; ops++;
/* #630: [op][line:32] = 5 bytes. */
if (i + 5 > chunk->code_len) { *stop_op = op; *stop_offset = i; break; }
i += 5; ops++;
} else {
*stop_op = op; *stop_offset = i;
break;
Expand Down Expand Up @@ -3835,8 +3836,11 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk,
}
i += 1;
} else { /* OP_LINE */
uint16_t line = (uint16_t)(chunk->code[i + 1] |
((uint16_t)chunk->code[i + 2] << 8));
/* #630: 32-bit operand. */
uint32_t line = (uint32_t)chunk->code[i + 1] |
((uint32_t)chunk->code[i + 2] << 8) |
((uint32_t)chunk->code[i + 3] << 16) |
((uint32_t)chunk->code[i + 4] << 24);
w = emit_movl_imm_at_disp32_rbx(w, g_layout.off_current_line,
(int32_t)line);
/* Also stamp g_trace_current_line (the line prev_record_assign
Expand All @@ -3846,7 +3850,7 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk,
* the line where the thunk took over. %rax is scratch between ops. */
w = emit_movabs_rax(w, (uint64_t)(uintptr_t)&g_trace_current_line);
w = emit_movl_imm_at_rax(w, (int32_t)line);
i += 3;
i += 5; /* #630: [op][line:32] */
}
/* Update last_imm in lockstep with the scanner's last_push_immediate.
* Consumed by OP_POP to decide between the `dec %ecx` peephole and
Expand Down
14 changes: 10 additions & 4 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,12 @@ static inline uint16_t read_u16(uint8_t *ip) {
return ip[0] | (ip[1] << 8);
}

/* #630: little-endian 32-bit operand read (OP_LINE). */
static inline uint32_t read_u32(uint8_t *ip) {
return (uint32_t)ip[0] | ((uint32_t)ip[1] << 8) |
((uint32_t)ip[2] << 16) | ((uint32_t)ip[3] << 24);
}

/* is_truthy declared in eigenscript.h */

/* Iterator state: stored as a list [iterable, index] */
Expand Down Expand Up @@ -1232,7 +1238,7 @@ static int vm_leaf_accessor_exec(EigsChunk *c, int argc) {
for (;;) {
switch (*ip++) {
case OP_LINE:
ip += 2;
ip += 4; /* #630: 32-bit operand */
break;
case OP_CONST: {
uint16_t idx = read_u16(ip); ip += 2;
Expand Down Expand Up @@ -5008,9 +5014,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume) {
}

CASE(LINE): {
uint16_t line = read_u16(ip); ip += 2;
current_line = line;
g_vm.current_line = line;
uint32_t line = read_u32(ip); ip += 4; /* #630: 32-bit operand */
current_line = (int)line;
g_vm.current_line = (int)line;
/* History stamping needs the current line whether or not a tape
* is open — a plain global store is far cheaper than the call
* (17.8M calls per 500k DMG-shaped steps before this change).
Expand Down
3 changes: 2 additions & 1 deletion src/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ typedef enum {
OP_MATCH, /* [case_count:16] pattern match dispatch */
OP_LISTCOMP_BEGIN, /* push empty list accumulator */
OP_LISTCOMP_APPEND, /* append TOS to accumulator */
OP_LINE, /* [line:16] update current line number */
OP_LINE, /* [line:32] update current line number (#630: was 16-bit, wrapped past line 65535) */
OP_WIDE, /* next operand is 32-bit */
OP_DISPATCH, /* pop arg, key, table; call table[key](arg) inline */

Expand Down Expand Up @@ -544,6 +544,7 @@ void chunk_decref(EigsChunk *chunk);
int chunk_add_constant(EigsChunk *chunk, Value *val);
void chunk_emit(EigsChunk *chunk, uint8_t byte, int line);
void chunk_emit_u16(EigsChunk *chunk, uint16_t val, int line);
void chunk_emit_u32(EigsChunk *chunk, uint32_t val, int line);
int chunk_emit_jump(EigsChunk *chunk, uint8_t op, int line);
void chunk_patch_jump(EigsChunk *chunk, int offset);
int chunk_add_function(EigsChunk *chunk, EigsChunk *fn);
Expand Down
48 changes: 48 additions & 0 deletions tests/run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,54 @@ else
fi
rm -f "$CPG_FILE"

# [120] OP_LINE 32-bit operand (#630). The line operand was u16, so every
# line past 65535 wrapped (line % 65536) and two assignments exactly 65536
# lines apart collapsed onto one stamp — 'what is x at L' then returned the
# wrong value at rc=0, and errors/traces past 65535 misreported the line.
# Generated at test time (a 70k-line file is not worth committing). Two
# assignments 65536 apart: x=111 at line A, x=222 at line A+65536. Both
# tiers must agree, so the JIT tier runs with thresholds forced low.
echo "[120] OP_LINE 32-bit line operand (#630)"
LW_FILE=$(mktemp /tmp/eigs_linewrap_XXXX.eigs)
python3 -c "
A = 4464
B = A + 65536
L = ['pad is 1'] * B
L[0] = 'x is 0'; L[A-1] = 'x is 111'; L[B-1] = 'x is 222'
body = ['r1 is what is x at %d' % A, 'print of r1',
'r2 is what is x at %d' % B, 'print of r2',
'boom is undefined_xyz_at_%d' % B]
print('\n'.join(L + body))" > "$LW_FILE"
TOTAL=$((TOTAL + 3))
# Interpreter tier
LW_INT=$(EIGS_JIT_OFF=1 ./eigenscript "$LW_FILE" </dev/null 2>&1)
# JIT tier (thresholds forced low so any hot region compiles)
LW_JIT=$(EIGS_JIT_ENTRY_THRESHOLD=1 EIGS_JIT_ITER_THRESHOLD=10 EIGS_JIT_OSR_THRESHOLD=50 \
./eigenscript "$LW_FILE" </dev/null 2>&1)
# 'what is x at 4464' must be 111 (not 222, the value that wrapped onto it).
LW_R1=$(printf '%s\n' "$LW_INT" | sed -n '1p')
LW_R2=$(printf '%s\n' "$LW_INT" | sed -n '2p')
# The error is on a line past 65535; a wrapped operand would report line %
# 65536 (a small number). The invariant is simply: reported line > 65535.
LW_ERRLINE=$(printf '%s\n' "$LW_INT" | grep -oE "Error line [0-9]+" | head -1 | grep -oE "[0-9]+")
if [ "$LW_R1" = "111" ] && [ "$LW_R2" = "222" ]; then
PASS=$((PASS + 1)); echo " PASS: 'what is x at L' correct past line 65535 (111, 222)"
else
FAIL=$((FAIL + 1)); echo " FAIL: temporal query wrapped (r1='$LW_R1' r2='$LW_R2', want 111/222)"
fi
if [ -n "$LW_ERRLINE" ] && [ "$LW_ERRLINE" -gt 65535 ]; then
PASS=$((PASS + 1)); echo " PASS: error line reported as $LW_ERRLINE (> 65535, not wrapped)"
else
FAIL=$((FAIL + 1)); echo " FAIL: error line wrapped ('$LW_ERRLINE', want > 65535)"
fi
if [ "$(printf '%s\n' "$LW_JIT" | sed -n '1,2p' | tr '\n' ',')" = "111,222," ]; then
PASS=$((PASS + 1)); echo " PASS: JIT tier agrees with interpreter past line 65535"
else
FAIL=$((FAIL + 1)); echo " FAIL: JIT/interpreter divergence past line 65535"
printf '%s\n' "$LW_JIT" | head -3
fi
rm -f "$LW_FILE"

# [116] Silent-tolerance audit batch-2 (#497/#498/#499/#501/#502/#511/#512).
# Builtins that used to return a silent null/0/""/wrong-order on invalid
# input now raise a catchable error from the closed error-kind set: matmul
Expand Down
Loading