Commitbdac740fRecorded15 Jan 2026Repositorysigil-app

docs: Add computed goto experimental results to JIT design notes

Message

Documents the findings from the feature/computed-goto branch: - Only ~1-2% improvement on modern CPUs with good branch predictors - GCC already generates efficient jump tables for switch statements - Added complexity (pragma warnings, broken fallback) not justified

The branch is preserved for future reference.

Changed
 notes/design/jit-support.md | 1405 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1405 insertions(+)
Diff
notes/design/jit-support.mdadded
@@ -0,0 +1,1405 @@
+1
# JIT Compilation Support for Sigil
+2
+3
This document describes the design for optional JIT (Just-In-Time) compilation support in Sigil. The goal is to provide meaningful performance improvements for compute-intensive code while maintaining Sigil's zero-dependency philosophy.
+4
+5
## Design Goals
+6
+7
1. **No external dependencies** - JIT compiles with only a C compiler, no LLVM/DynASM/etc.
+8
2. **Optional feature** - Sigil works fine without JIT; it's an optimization, not a requirement
+9
3. **Incremental adoption** - Start with x86-64, add ARM64 later
+10
4. **Graceful fallback** - Unknown patterns fall back to interpreter
+11
5. **Minimal complexity** - Target 2-5x speedup for numeric code, not 20x
+12
+13
## Architecture Overview
+14
+15
```
+16
┌─────────────────────────────────────────────────────────────┐
+17
│ Sigil VM │
+18
├─────────────────────────────────────────────────────────────┤
+19
│ Interpreter (vm.c) │
+20
│ │ │
+21
│ ├─→ Profile call counts │
+22
│ │ │
+23
│ └─→ Hot function detected (>N calls) │
+24
│ │ │
+25
│ ▼ │
+26
│ ┌─────────────────┐ ┌─────────────────┐ │
+27
│ │ JIT Compiler │───→│ Code Buffer │ │
+28
│ │ (jit.c) │ │ (executable) │ │
+29
│ └─────────────────┘ └─────────────────┘ │
+30
│ │ │ │
+31
│ ▼ ▼ │
+32
│ ┌─────────────────┐ ┌─────────────────┐ │
+33
│ │ x86-64 Encoder │ │ Native Code │ │
+34
│ │ (jit-x64.c) │ │ Execution │ │
+35
│ └─────────────────┘ └─────────────────┘ │
+36
└─────────────────────────────────────────────────────────────┘
+37
```
+38
+39
### Components
+40
+41
| File | Purpose |
+42
|------|---------|
+43
| `jit.h` | Public JIT API and data structures |
+44
| `jit.c` | JIT compiler: bytecode → IR → native |
+45
| `jit-x64.c` | x86-64 instruction encoder |
+46
| `jit-arm64.c` | ARM64 instruction encoder (future) |
+47
+48
## Code Buffer Management
+49
+50
The code buffer handles executable memory allocation:
+51
+52
```c
+53
// jit.h
+54
+55
typedef struct {
+56
uint8_t *base; // Start of allocated region
+57
uint8_t *ptr; // Current write position
+58
uint8_t *limit; // End of allocated region
+59
size_t capacity; // Total size
+60
} JitCodeBuf;
+61
+62
// Allocate executable memory
+63
JitCodeBuf *jit_codebuf_create(size_t initial_size);
+64
+65
// Free executable memory
+66
void jit_codebuf_destroy(JitCodeBuf *buf);
+67
+68
// Ensure space for n bytes, grow if needed
+69
bool jit_codebuf_ensure(JitCodeBuf *buf, size_t n);
+70
+71
// Get current code size
+72
static inline size_t jit_codebuf_size(JitCodeBuf *buf) {
+73
return buf->ptr - buf->base;
+74
}
+75
+76
// Get function pointer to start of buffer
+77
static inline void *jit_codebuf_fn(JitCodeBuf *buf) {
+78
return (void *)buf->base;
+79
}
+80
```
+81
+82
### Platform-Specific Implementation
+83
+84
```c
+85
// jit.c
+86
+87
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+88
#include <sys/mman.h>
+89
+90
JitCodeBuf *jit_codebuf_create(size_t size)
+91
{
+92
JitCodeBuf *buf = malloc(sizeof(JitCodeBuf));
+93
if (!buf) return NULL;
+94
+95
// Round up to page size
+96
size_t page_size = sysconf(_SC_PAGESIZE);
+97
size = (size + page_size - 1) & ~(page_size - 1);
+98
+99
void *mem = mmap(NULL, size,
+100
PROT_READ | PROT_WRITE | PROT_EXEC,
+101
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+102
+103
if (mem == MAP_FAILED) {
+104
free(buf);
+105
return NULL;
+106
}
+107
+108
buf->base = mem;
+109
buf->ptr = mem;
+110
buf->limit = (uint8_t *)mem + size;
+111
buf->capacity = size;
+112
+113
return buf;
+114
}
+115
+116
void jit_codebuf_destroy(JitCodeBuf *buf)
+117
{
+118
if (buf) {
+119
munmap(buf->base, buf->capacity);
+120
free(buf);
+121
}
+122
}
+123
+124
#elif defined(__APPLE__)
+125
#include <sys/mman.h>
+126
#include <pthread.h>
+127
+128
JitCodeBuf *jit_codebuf_create(size_t size)
+129
{
+130
JitCodeBuf *buf = malloc(sizeof(JitCodeBuf));
+131
if (!buf) return NULL;
+132
+133
size_t page_size = sysconf(_SC_PAGESIZE);
+134
size = (size + page_size - 1) & ~(page_size - 1);
+135
+136
// macOS requires MAP_JIT for Apple Silicon
+137
void *mem = mmap(NULL, size,
+138
PROT_READ | PROT_WRITE | PROT_EXEC,
+139
MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT, -1, 0);
+140
+141
if (mem == MAP_FAILED) {
+142
free(buf);
+143
return NULL;
+144
}
+145
+146
buf->base = mem;
+147
buf->ptr = mem;
+148
buf->limit = (uint8_t *)mem + size;
+149
buf->capacity = size;
+150
+151
return buf;
+152
}
+153
+154
// Apple Silicon requires toggling write protection
+155
void jit_codebuf_begin_write(JitCodeBuf *buf)
+156
{
+157
#if defined(__aarch64__)
+158
pthread_jit_write_protect_np(0); // Disable write protection
+159
#endif
+160
(void)buf;
+161
}
+162
+163
void jit_codebuf_end_write(JitCodeBuf *buf)
+164
{
+165
#if defined(__aarch64__)
+166
pthread_jit_write_protect_np(1); // Re-enable write protection
+167
sys_icache_invalidate(buf->base, jit_codebuf_size(buf));
+168
#endif
+169
(void)buf;
+170
}
+171
+172
#elif defined(_WIN32)
+173
#include <windows.h>
+174
+175
JitCodeBuf *jit_codebuf_create(size_t size)
+176
{
+177
JitCodeBuf *buf = malloc(sizeof(JitCodeBuf));
+178
if (!buf) return NULL;
+179
+180
SYSTEM_INFO si;
+181
GetSystemInfo(&si);
+182
size = (size + si.dwPageSize - 1) & ~(si.dwPageSize - 1);
+183
+184
void *mem = VirtualAlloc(NULL, size,
+185
MEM_COMMIT | MEM_RESERVE,
+186
PAGE_EXECUTE_READWRITE);
+187
+188
if (!mem) {
+189
free(buf);
+190
return NULL;
+191
}
+192
+193
buf->base = mem;
+194
buf->ptr = mem;
+195
buf->limit = (uint8_t *)mem + size;
+196
buf->capacity = size;
+197
+198
return buf;
+199
}
+200
+201
void jit_codebuf_destroy(JitCodeBuf *buf)
+202
{
+203
if (buf) {
+204
VirtualFree(buf->base, 0, MEM_RELEASE);
+205
free(buf);
+206
}
+207
}
+208
+209
#endif
+210
```
+211
+212
## x86-64 Instruction Encoder
+213
+214
### Register Definitions
+215
+216
```c
+217
// jit-x64.h
+218
+219
typedef enum {
+220
RAX = 0, RCX = 1, RDX = 2, RBX = 3,
+221
RSP = 4, RBP = 5, RSI = 6, RDI = 7,
+222
R8 = 8, R9 = 9, R10 = 10, R11 = 11,
+223
R12 = 12, R13 = 13, R14 = 14, R15 = 15
+224
} X64Reg;
+225
+226
// Calling convention (System V AMD64 ABI)
+227
// Arguments: RDI, RSI, RDX, RCX, R8, R9
+228
// Return: RAX
+229
// Callee-saved: RBX, RBP, R12-R15
+230
// Caller-saved: RAX, RCX, RDX, RSI, RDI, R8-R11
+231
+232
#define ARG1 RDI
+233
#define ARG2 RSI
+234
#define ARG3 RDX
+235
#define ARG4 RCX
+236
#define ARG5 R8
+237
#define ARG6 R9
+238
#define RET RAX
+239
+240
// Scratch registers for JIT use
+241
#define SCRATCH1 R10
+242
#define SCRATCH2 R11
+243
```
+244
+245
### Encoding Helpers
+246
+247
```c
+248
// jit-x64.c
+249
+250
// Emit raw bytes
+251
static inline void emit_u8(JitCodeBuf *buf, uint8_t v)
+252
{
+253
*buf->ptr++ = v;
+254
}
+255
+256
static inline void emit_u16(JitCodeBuf *buf, uint16_t v)
+257
{
+258
memcpy(buf->ptr, &v, 2);
+259
buf->ptr += 2;
+260
}
+261
+262
static inline void emit_u32(JitCodeBuf *buf, uint32_t v)
+263
{
+264
memcpy(buf->ptr, &v, 4);
+265
buf->ptr += 4;
+266
}
+267
+268
static inline void emit_u64(JitCodeBuf *buf, uint64_t v)
+269
{
+270
memcpy(buf->ptr, &v, 8);
+271
buf->ptr += 8;
+272
}
+273
+274
static inline void emit_i32(JitCodeBuf *buf, int32_t v)
+275
{
+276
memcpy(buf->ptr, &v, 4);
+277
buf->ptr += 4;
+278
}
+279
+280
// REX prefix for 64-bit operations
+281
// W=1 for 64-bit operand size
+282
// R=1 if ModRM.reg is R8-R15
+283
// X=1 if SIB.index is R8-R15
+284
// B=1 if ModRM.rm or SIB.base is R8-R15
+285
static inline uint8_t rex(bool w, bool r, bool x, bool b)
+286
{
+287
return 0x40 | (w << 3) | (r << 2) | (x << 1) | b;
+288
}
+289
+290
static inline uint8_t rex_wrb(X64Reg reg, X64Reg rm)
+291
{
+292
return rex(true, reg >= R8, false, rm >= R8);
+293
}
+294
+295
// ModRM byte: mod(2) | reg(3) | rm(3)
+296
static inline uint8_t modrm(uint8_t mod, uint8_t reg, uint8_t rm)
+297
{
+298
return (mod << 6) | ((reg & 7) << 3) | (rm & 7);
+299
}
+300
+301
// ModRM for register-to-register
+302
static inline uint8_t modrm_rr(X64Reg reg, X64Reg rm)
+303
{
+304
return modrm(0b11, reg & 7, rm & 7);
+305
}
+306
```
+307
+308
### Core Instructions
+309
+310
```c
+311
// jit-x64.c
+312
+313
// MOV reg, imm64
+314
void x64_mov_reg_imm64(JitCodeBuf *buf, X64Reg dst, uint64_t imm)
+315
{
+316
emit_u8(buf, rex(true, false, false, dst >= R8));
+317
emit_u8(buf, 0xb8 + (dst & 7)); // MOV r64, imm64
+318
emit_u64(buf, imm);
+319
}
+320
+321
// MOV reg, imm32 (zero-extended to 64-bit)
+322
void x64_mov_reg_imm32(JitCodeBuf *buf, X64Reg dst, uint32_t imm)
+323
{
+324
if (dst >= R8) {
+325
emit_u8(buf, rex(false, false, false, true));
+326
}
+327
emit_u8(buf, 0xb8 + (dst & 7)); // MOV r32, imm32
+328
emit_u32(buf, imm);
+329
}
+330
+331
// MOV reg, reg
+332
void x64_mov_reg_reg(JitCodeBuf *buf, X64Reg dst, X64Reg src)
+333
{
+334
emit_u8(buf, rex_wrb(src, dst));
+335
emit_u8(buf, 0x89); // MOV r/m64, r64
+336
emit_u8(buf, modrm_rr(src, dst));
+337
}
+338
+339
// MOV reg, [reg + offset]
+340
void x64_mov_reg_mem(JitCodeBuf *buf, X64Reg dst, X64Reg base, int32_t offset)
+341
{
+342
emit_u8(buf, rex_wrb(dst, base));
+343
emit_u8(buf, 0x8b); // MOV r64, r/m64
+344
+345
if (offset == 0 && (base & 7) != RBP) {
+346
emit_u8(buf, modrm(0b00, dst & 7, base & 7));
+347
if ((base & 7) == RSP) emit_u8(buf, 0x24); // SIB for RSP
+348
} else if (offset >= -128 && offset <= 127) {
+349
emit_u8(buf, modrm(0b01, dst & 7, base & 7));
+350
if ((base & 7) == RSP) emit_u8(buf, 0x24);
+351
emit_u8(buf, (uint8_t)offset);
+352
} else {
+353
emit_u8(buf, modrm(0b10, dst & 7, base & 7));
+354
if ((base & 7) == RSP) emit_u8(buf, 0x24);
+355
emit_i32(buf, offset);
+356
}
+357
}
+358
+359
// MOV [reg + offset], reg
+360
void x64_mov_mem_reg(JitCodeBuf *buf, X64Reg base, int32_t offset, X64Reg src)
+361
{
+362
emit_u8(buf, rex_wrb(src, base));
+363
emit_u8(buf, 0x89); // MOV r/m64, r64
+364
+365
if (offset == 0 && (base & 7) != RBP) {
+366
emit_u8(buf, modrm(0b00, src & 7, base & 7));
+367
if ((base & 7) == RSP) emit_u8(buf, 0x24);
+368
} else if (offset >= -128 && offset <= 127) {
+369
emit_u8(buf, modrm(0b01, src & 7, base & 7));
+370
if ((base & 7) == RSP) emit_u8(buf, 0x24);
+371
emit_u8(buf, (uint8_t)offset);
+372
} else {
+373
emit_u8(buf, modrm(0b10, src & 7, base & 7));
+374
if ((base & 7) == RSP) emit_u8(buf, 0x24);
+375
emit_i32(buf, offset);
+376
}
+377
}
+378
+379
// ADD reg, reg
+380
void x64_add_reg_reg(JitCodeBuf *buf, X64Reg dst, X64Reg src)
+381
{
+382
emit_u8(buf, rex_wrb(src, dst));
+383
emit_u8(buf, 0x01); // ADD r/m64, r64
+384
emit_u8(buf, modrm_rr(src, dst));
+385
}
+386
+387
// ADD reg, imm32
+388
void x64_add_reg_imm32(JitCodeBuf *buf, X64Reg dst, int32_t imm)
+389
{
+390
emit_u8(buf, rex(true, false, false, dst >= R8));
+391
if (imm >= -128 && imm <= 127) {
+392
emit_u8(buf, 0x83); // ADD r/m64, imm8
+393
emit_u8(buf, modrm(0b11, 0, dst & 7));
+394
emit_u8(buf, (uint8_t)imm);
+395
} else {
+396
emit_u8(buf, 0x81); // ADD r/m64, imm32
+397
emit_u8(buf, modrm(0b11, 0, dst & 7));
+398
emit_i32(buf, imm);
+399
}
+400
}
+401
+402
// SUB reg, reg
+403
void x64_sub_reg_reg(JitCodeBuf *buf, X64Reg dst, X64Reg src)
+404
{
+405
emit_u8(buf, rex_wrb(src, dst));
+406
emit_u8(buf, 0x29); // SUB r/m64, r64
+407
emit_u8(buf, modrm_rr(src, dst));
+408
}
+409
+410
// SUB reg, imm32
+411
void x64_sub_reg_imm32(JitCodeBuf *buf, X64Reg dst, int32_t imm)
+412
{
+413
emit_u8(buf, rex(true, false, false, dst >= R8));
+414
if (imm >= -128 && imm <= 127) {
+415
emit_u8(buf, 0x83); // SUB r/m64, imm8
+416
emit_u8(buf, modrm(0b11, 5, dst & 7));
+417
emit_u8(buf, (uint8_t)imm);
+418
} else {
+419
emit_u8(buf, 0x81); // SUB r/m64, imm32
+420
emit_u8(buf, modrm(0b11, 5, dst & 7));
+421
emit_i32(buf, imm);
+422
}
+423
}
+424
+425
// IMUL reg, reg (signed multiply)
+426
void x64_imul_reg_reg(JitCodeBuf *buf, X64Reg dst, X64Reg src)
+427
{
+428
emit_u8(buf, rex_wrb(dst, src));
+429
emit_u8(buf, 0x0f);
+430
emit_u8(buf, 0xaf); // IMUL r64, r/m64
+431
emit_u8(buf, modrm_rr(dst, src));
+432
}
+433
+434
// CMP reg, reg
+435
void x64_cmp_reg_reg(JitCodeBuf *buf, X64Reg a, X64Reg b)
+436
{
+437
emit_u8(buf, rex_wrb(b, a));
+438
emit_u8(buf, 0x39); // CMP r/m64, r64
+439
emit_u8(buf, modrm_rr(b, a));
+440
}
+441
+442
// CMP reg, imm32
+443
void x64_cmp_reg_imm32(JitCodeBuf *buf, X64Reg reg, int32_t imm)
+444
{
+445
emit_u8(buf, rex(true, false, false, reg >= R8));
+446
if (imm >= -128 && imm <= 127) {
+447
emit_u8(buf, 0x83); // CMP r/m64, imm8
+448
emit_u8(buf, modrm(0b11, 7, reg & 7));
+449
emit_u8(buf, (uint8_t)imm);
+450
} else {
+451
emit_u8(buf, 0x81); // CMP r/m64, imm32
+452
emit_u8(buf, modrm(0b11, 7, reg & 7));
+453
emit_i32(buf, imm);
+454
}
+455
}
+456
+457
// TEST reg, reg (for zero checks)
+458
void x64_test_reg_reg(JitCodeBuf *buf, X64Reg a, X64Reg b)
+459
{
+460
emit_u8(buf, rex_wrb(b, a));
+461
emit_u8(buf, 0x85); // TEST r/m64, r64
+462
emit_u8(buf, modrm_rr(b, a));
+463
}
+464
+465
// AND reg, imm32
+466
void x64_and_reg_imm32(JitCodeBuf *buf, X64Reg dst, int32_t imm)
+467
{
+468
emit_u8(buf, rex(true, false, false, dst >= R8));
+469
if (imm >= -128 && imm <= 127) {
+470
emit_u8(buf, 0x83);
+471
emit_u8(buf, modrm(0b11, 4, dst & 7));
+472
emit_u8(buf, (uint8_t)imm);
+473
} else {
+474
emit_u8(buf, 0x81);
+475
emit_u8(buf, modrm(0b11, 4, dst & 7));
+476
emit_i32(buf, imm);
+477
}
+478
}
+479
+480
// OR reg, imm32
+481
void x64_or_reg_imm32(JitCodeBuf *buf, X64Reg dst, int32_t imm)
+482
{
+483
emit_u8(buf, rex(true, false, false, dst >= R8));
+484
if (imm >= -128 && imm <= 127) {
+485
emit_u8(buf, 0x83);
+486
emit_u8(buf, modrm(0b11, 1, dst & 7));
+487
emit_u8(buf, (uint8_t)imm);
+488
} else {
+489
emit_u8(buf, 0x81);
+490
emit_u8(buf, modrm(0b11, 1, dst & 7));
+491
emit_i32(buf, imm);
+492
}
+493
}
+494
+495
// SHR reg, imm8 (logical right shift)
+496
void x64_shr_reg_imm8(JitCodeBuf *buf, X64Reg dst, uint8_t imm)
+497
{
+498
emit_u8(buf, rex(true, false, false, dst >= R8));
+499
if (imm == 1) {

Showing the first 500 of 1406 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.