Skip to content

Commit bec06db

Browse files
bmdhacksclaude
andcommitted
fix arm64 jit: multiple correctness and performance fixes
- Exception type filtering: OTrap now looks ahead at catch handler opcodes to set tcheck for typed exception catches, matching x86 - hl_jit_free: properly clean up all allocator state and support can_reset for hot reload, fixing memory leaks - OAssert: use correct LDR+BLR+B+literal pool pattern instead of broken literal+BL sequence that was never patched - OSwitch: replace O(n) linear CMP/B.EQ scan with O(1) branch table using ADR+ADD+BR - Size encoding: large-offset paths in op_get_mem/op_set_mem now correctly handle 1-byte and 2-byte access sizes Inspired by review of #857. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 15cd29d commit bec06db

1 file changed

Lines changed: 92 additions & 39 deletions

File tree

src/jit_aarch64.c

Lines changed: 92 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,10 +2118,11 @@ static void op_get_mem(jit_ctx *ctx, vreg *dst, vreg *base, int offset, int size
21182118
encode_ldr_str_imm(ctx, size_bits, 0, 0x01, imm12, base_r, dst_r);
21192119
} else {
21202120
// Offset too large - compute effective address in RTMP, then load into dst_r
2121+
int size_bits = (size == 1) ? 0x00 : (size == 2) ? 0x01 : (size == 4) ? 0x02 : 0x03;
21212122
load_immediate(ctx, offset, RTMP, false); // Use RTMP for address computation
21222123
encode_add_sub_reg(ctx, 1, 0, 0, 0, RTMP, 0, base_r, RTMP);
21232124
// LDR dst_r, [RTMP]
2124-
encode_ldr_str_imm(ctx, (size == 8) ? 0x03 : 0x02, 0, 0x01, 0, RTMP, dst_r);
2125+
encode_ldr_str_imm(ctx, size_bits, 0, 0x01, 0, RTMP, dst_r);
21252126
}
21262127

21272128
// Always store to stack - it's the source of truth for later loads
@@ -2206,7 +2207,10 @@ static void op_set_mem(jit_ctx *ctx, vreg *base, int offset, vreg *value, int si
22062207
encode_add_sub_reg(ctx, 1, 0, 0, 0, RTMP2, 0, base_r, RTMP);
22072208
}
22082209
// STR value_r, [RTMP]
2209-
encode_ldr_str_imm(ctx, (size == 8) ? 0x03 : 0x02, 0, 0x00, 0, RTMP, value_r);
2210+
{
2211+
int size_bits = (size == 1) ? 0x00 : (size == 2) ? 0x01 : (size == 4) ? 0x02 : 0x03;
2212+
encode_ldr_str_imm(ctx, size_bits, 0, 0x00, 0, RTMP, value_r);
2213+
}
22102214
}
22112215
}
22122216

@@ -4136,14 +4140,22 @@ void hl_jit_free(jit_ctx *ctx, h_bool can_reset) {
41364140
if (ctx == NULL)
41374141
return;
41384142

4139-
if (ctx->startBuf)
4140-
free(ctx->startBuf);
4141-
if (ctx->vregs)
4142-
free(ctx->vregs);
4143-
if (ctx->opsPos)
4144-
free(ctx->opsPos);
4145-
4146-
free(ctx);
4143+
free(ctx->vregs);
4144+
free(ctx->opsPos);
4145+
free(ctx->startBuf);
4146+
ctx->maxRegs = 0;
4147+
ctx->vregs = NULL;
4148+
ctx->maxOps = 0;
4149+
ctx->opsPos = NULL;
4150+
ctx->startBuf = NULL;
4151+
ctx->bufSize = 0;
4152+
ctx->buf.b = NULL;
4153+
ctx->calls = NULL;
4154+
ctx->switchs = NULL;
4155+
ctx->closure_list = NULL;
4156+
hl_free(&ctx->falloc);
4157+
hl_free(&ctx->galloc);
4158+
if (!can_reset) free(ctx);
41474159
}
41484160

41494161
void hl_jit_reset(jit_ctx *ctx, hl_module *m) {
@@ -5765,32 +5777,48 @@ int hl_jit_function(jit_ctx *ctx, hl_module *m, hl_function *f) {
57655777
break;
57665778

57675779
case OSwitch:
5768-
// Switch statement - simplified linear search approach
5780+
// Switch statement - O(1) branch table dispatch
57695781
{
57705782
// Spill all registers before any conditional jumps.
57715783
// Jump targets will have discard_regs() via OLabel.
57725784
spill_regs(ctx);
57735785

57745786
// Fetch the switch value (this creates a new binding after spill)
57755787
preg *r_val = fetch(ctx, dst);
5788+
Arm64Reg val_r = (Arm64Reg)r_val->id;
5789+
5790+
// CMP value, #count (bounds check)
5791+
if (o->p2 < 4096) {
5792+
encode_add_sub_imm(ctx, 0, 1, 1, 0, o->p2, val_r, XZR);
5793+
} else {
5794+
load_immediate(ctx, o->p2, RTMP, false);
5795+
encode_add_sub_reg(ctx, 0, 1, 1, 0, RTMP, 0, val_r, XZR);
5796+
}
5797+
5798+
// B.HS default (unsigned >= count means out of range)
5799+
int jdefault = BUF_POS();
5800+
encode_branch_cond(ctx, 0, COND_HS);
5801+
5802+
// ADR RTMP, table_start (3 instructions ahead = +12 bytes)
5803+
// ADR encodes PC-relative: immhi = imm>>2, immlo = imm&3
5804+
EMIT32(ctx, 0x10000000 | ((12 & 3) << 29) | ((12 >> 2) << 5) | RTMP);
5805+
5806+
// ADD RTMP, RTMP, Wn, UXTW #2 (index * 4 bytes per B instruction)
5807+
encode_add_sub_ext(ctx, 1, 0, 0, val_r, 2, 2, RTMP, RTMP);
57765808

5777-
// For each case, compare and branch
5809+
// BR RTMP
5810+
EMIT32(ctx, 0xD61F0000 | (RTMP << 5));
5811+
5812+
// Branch table: one B instruction per case
57785813
for (int i = 0; i < o->p2; i++) {
5779-
// CMP value, #i
5780-
if (i < 256) {
5781-
// SUBS XZR, Xn, #i
5782-
encode_add_sub_imm(ctx, (dst->size == 8) ? 1 : 0, 1, 1, 0, i, (Arm64Reg)r_val->id, XZR);
5783-
} else {
5784-
load_immediate(ctx, i, RTMP, false);
5785-
encode_add_sub_reg(ctx, (dst->size == 8) ? 1 : 0, 1, 1, 0, RTMP, 0, (Arm64Reg)r_val->id, XZR);
5786-
}
5787-
5788-
// B.EQ to target
57895814
int jump_pos = BUF_POS();
5790-
encode_branch_cond(ctx, 0, COND_EQ);
5815+
EMIT32(ctx, 0x14000000); // B #0 (placeholder, patched by register_jump)
57915816
register_jump(ctx, jump_pos, (opCount + 1) + o->extra[i]);
5817+
if ((i & 15) == 0) jit_buf(ctx);
57925818
}
5793-
// If no match, fall through (default case is next instruction)
5819+
5820+
// Default: patch B.HS to here
5821+
patch_jump(ctx, jdefault, BUF_POS());
57945822
}
57955823
break;
57965824

@@ -5875,13 +5903,37 @@ int hl_jit_function(jit_ctx *ctx, hl_module *m, hl_function *f) {
58755903
encode_ldr_str_reg(ctx, 0x03, 0, 0x00, RTMP, 0x03, 0, X0, X10);
58765904
}
58775905

5878-
// Step 4: Set tcheck = NULL (simplified - not doing type filtering yet)
5879-
// STR XZR, [SP, #offset_tcheck]
5880-
if (offset_tcheck < 4096) {
5881-
encode_ldr_str_imm(ctx, 0x03, 0, 0x00, offset_tcheck / 8, X10, XZR);
5882-
} else {
5883-
load_immediate(ctx, offset_tcheck, RTMP, true);
5884-
encode_ldr_str_reg(ctx, 0x03, 0, 0x00, RTMP, 0x03, 0, X10, XZR);
5906+
// Step 4: Set tcheck for exception type filtering
5907+
// Look ahead at catch handler opcodes to determine the exception type
5908+
// (same pattern as x86 backend)
5909+
{
5910+
hl_opcode *cat = f->ops + opCount + 1;
5911+
hl_opcode *next = f->ops + opCount + 1 + o->p2;
5912+
hl_opcode *next2 = f->ops + opCount + 2 + o->p2;
5913+
Arm64Reg tcheck_val = XZR; // default: NULL = catch all
5914+
5915+
if (cat->op == OCatch ||
5916+
(next->op == OGetGlobal && next2->op == OCall2 &&
5917+
next2->p3 == next->p1 && dst->stack.id == (int)(int_val)next2->extra)) {
5918+
int gindex = cat->op == OCatch ? cat->p1 : next->p2;
5919+
hl_type *gt = ctx->m->code->globals[gindex];
5920+
while (gt->kind == HOBJ && gt->obj->super) gt = gt->obj->super;
5921+
if (gt->kind == HOBJ && gt->obj->nfields && gt->obj->fields[0].t->kind == HTYPE) {
5922+
void *addr = ctx->m->globals_data + ctx->m->globals_indexes[gindex];
5923+
// Load address of the global, then dereference to get the type object
5924+
load_immediate(ctx, (int64_t)addr, X9, true);
5925+
encode_ldr_str_imm(ctx, 0x03, 0, 0x01, 0, X9, X9); // LDR X9, [X9]
5926+
tcheck_val = X9;
5927+
}
5928+
}
5929+
5930+
// STR tcheck_val, [SP, #offset_tcheck]
5931+
if (offset_tcheck < 4096) {
5932+
encode_ldr_str_imm(ctx, 0x03, 0, 0x00, offset_tcheck / 8, X10, tcheck_val);
5933+
} else {
5934+
load_immediate(ctx, offset_tcheck, RTMP, true);
5935+
encode_ldr_str_reg(ctx, 0x03, 0, 0x00, RTMP, 0x03, 0, X10, tcheck_val);
5936+
}
58855937
}
58865938

58875939
// Step 5: Call setjmp(trap_ctx)
@@ -6017,20 +6069,21 @@ int hl_jit_function(jit_ctx *ctx, hl_module *m, hl_function *f) {
60176069
break;
60186070

60196071
case OAssert:
6020-
// Call the assertion helper (static_functions[1])
6072+
// Call the assertion helper (hl_assert)
6073+
// Use same LDR+BLR+B+literal pattern as JIT function calls
60216074
{
6075+
EMIT32(ctx, 0x58000071); // LDR X17, #12 (load from literal pool)
6076+
EMIT32(ctx, 0xD63F0220); // BLR X17
6077+
60226078
jlist *j = (jlist*)hl_malloc(&ctx->galloc, sizeof(jlist));
6023-
j->pos = BUF_POS();
6079+
j->pos = BUF_POS() + 4; // Position of the 8-byte literal (after B)
60246080
j->target = -2; // Special marker for assert function
60256081
j->next = ctx->calls;
60266082
ctx->calls = j;
60276083

6028-
// Load address placeholder and call
6029-
// Will be patched to static_functions[1] in hl_jit_code
6030-
EMIT32(ctx, 0); // Placeholder for address (low)
6031-
EMIT32(ctx, 0); // Placeholder for address (high)
6032-
// For now, emit a BL that will be patched
6033-
EMIT32(ctx, 0x94000000); // BL (will be patched)
6084+
EMIT32(ctx, 0x14000003); // B #12 (skip over literal)
6085+
EMIT32(ctx, 0); // Low 32 bits placeholder
6086+
EMIT32(ctx, 0); // High 32 bits placeholder
60346087
}
60356088
break;
60366089

0 commit comments

Comments
 (0)