diff --git a/README.md b/README.md
index 58f8034..ff51f6a 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
A high-performance systems language with built-in concurrency and ML — without GC.
Bootstrapped from raw x86-64 assembly. The compiler compiles itself.
+ Website •
Install •
Docs •
Stdlib •
@@ -23,13 +24,15 @@
- **Self-hosted** — the compiler is written entirely in Jda (zero C/C++/Rust)
- **Bootstrapped from assembly** — no external compiler dependency
- **33x faster compilation than Rust** — 42ms average compile time ([benchmarks](benchmarks/RESULTS.md))
-- **Beats C on 2 of 4 benchmarks** — faster than gcc -O2 on sieve and sum loop
+- **Beats C on sudoku and LZ77** — 41ms vs 62ms (sudoku), 277ms vs 1830ms (LZ77) — even running via Rosetta 2 x86-64
+- **6.6× faster than C on LZ77** — hash-chain compression with source-level optimizations outperforms all languages tested
- **24–53x faster than Python/Ruby** — compiled performance with scripting-speed iteration
- **117 stdlib packages** — data structures, networking, crypto, JSON, HTTP, ML/AI, debugging, profiling, and more
- **388 conformance tests** — all passing
- **Cross-platform** — native on Linux, Docker-based on macOS/Windows
- **Built-in concurrency** — goroutine-style green threads with channels
- **ML primitives** — tensors, autograd, neural networks, AVX-512 acceleration
+- **Jda Forge** — high-performance web framework built entirely in Jda ([Website](https://www.jdalang.org/forge/))
## Current Status
@@ -108,6 +111,15 @@ All three compile to **< 1.1 MB static ELF binaries** with zero external depende
**[→ Getting Started Guides](docs/getting-started/)** — build a CLI tool, an HTTP server, or train a neural network step by step.
+## Ecosystem
+
+### Jda Forge
+**[Jda Forge](https://www.jdalang.org/forge/)** is a high-performance web and application framework built entirely in Jda. It leverages the language's native concurrency and systems-level efficiency to provide a modern, type-safe foundation for scalable services.
+
+- **Website**: [jdalang.org/forge](https://www.jdalang.org/forge/)
+- **GitHub**: [jdalang/jda-forge](https://github.com/jdalang/jda-forge)
+- **Built in Jda**: The framework is 100% Jda code, showcasing the language's capability for building complex, high-level abstractions.
+
## Language Features
**Core**: functions, structs, arrays, pointers, references, if/else, loops, const, enums, generics, closures, pattern matching, inline assembly
@@ -284,32 +296,51 @@ See [docs/language/structs.md](docs/language/structs.md) for the full OOP guide.
Jda: **zero external dependencies** — bootstrapped from assembly, single static binary.
-### Complex Benchmarks (Full Source Code)
+### Complex Benchmarks — macOS Native (ms, lower is better)
-Real-world problems with complete source in all 6 languages — [full analysis with code](benchmarks/COMPLEX_BENCHMARKS.md):
+Real algorithms with full source in all 6 languages. Measured on macOS Apple Silicon — C/Rust/Go compile to native ARM64; **Jda runs x86-64 via Rosetta 2** (ISA handicap).
-| Problem | C | Jda | Rust | Go | Python | Ruby |
-|---------|----:|--------:|------:|----:|-------:|-----:|
-| JSON parse+filter 100K | 18 | **16** | 19 | 42 | 189 | 312 |
-| Sieve 10M | 38 | **34** | 42 | 48 | 4,820 | 3,910 |
-| Levenshtein 5Kx5K | 42 | 45 | 40 | 58 | 12,400 | 8,900 |
-| SHA-256 chain 100K | 85 | 92 | 82 | 110 | 38,500 | 22,100 |
-| N-Queens N=14 | 210 | 245 | 195 | 320 | 82,000 | 41,500 |
-| HTTP parse 50K | 12 | 14 | 11 | 28 | 420 | 680 |
+| Problem | C | Rust | Go | **Jda** | Ruby | Python |
+|---------|--:|-----:|---:|--------:|-----:|-------:|
+| Sudoku — 500 puzzles | 62 | 62 | 66 | **41** | 3,854 | 1,753 |
+| LZ77 — 1 MB compress | 1,830 | 2,185 | 2,721 | **277** | 222,424 | — |
+| Regex — 8 pats × 100K | 98 | 221 | 813 | 186 | 7,940 | 7,406 |
+| B-Tree — 1M insert/lookup | 282 | 297 | 318 | 586 | 11,529 | 10,955 |
+| Raytracer — 800×600 | 19 | 21 | 35 | 331 | 3,301 | 4,080 |
-**Jda wins 2/6 vs C**, **6/6 vs Go**, **208x faster than Python**, **116x faster than Ruby**. Within 8-17% of C on all problems.
+**Highlights:**
+- **Sudoku**: Jda **1.5× faster than C/Rust** (source-level constraint propagation + DCE)
+- **LZ77**: Jda **6.6× faster than C** — MOD→AND strength reduction + hash-chain hoisting eliminate the bottleneck entirely
+- **Regex**: Jda beats Go (813ms) and Rust (221ms); Thompson NFA + DFA subset construction
+- **BTree / Raytracer**: gap to C remains — Rosetta 2 overhead + scalar x86 vs ARM SIMD
-Reproduce
+Reproduce locally
```bash
-docker build --platform linux/amd64 -t jda-bench benchmarks/
+# Compile Jda benchmarks (requires jda1 binary in bootstrap/stage0/)
docker run --rm --platform linux/amd64 --ulimit stack=524288000:524288000 \
- -v $(pwd):/jda -w /jda jda-bench bash /jda/benchmarks/run.sh
+ -v $(PWD):/jda -w /jda/bootstrap/stage0 jda-build \
+ sh -c "./jda1 build --macos /jda/benchmarks/complex/sudoku/sudoku.jda -o sudoku_mac"
+codesign -s - sudoku_mac && ./sudoku_mac < benchmarks/complex/sudoku/puzzles.txt
+
+# C, Go, Rust — compile natively
+clang -O2 -o sudoku_c benchmarks/complex/sudoku/sudoku.c
+go build -o sudoku_go benchmarks/complex/sudoku/sudoku.go
+rustc -O -o sudoku_rs benchmarks/complex/sudoku/sudoku.rs
```
+## Jda Forge
+
+**[Jda Forge](https://www.jdalang.org/forge/)** is a full-stack web framework built entirely in Jda — no C, no dependencies. It provides HTTP routing, middleware, templating, and a database layer, all written in the language itself.
+
+- Website: [jdalang.org/forge](https://www.jdalang.org/forge/)
+- GitHub: [github.com/jdalang/jda-forge](https://github.com/jdalang/jda-forge)
+
+Because Forge is written in Jda, it serves as a real-world proof that the language is ready for production web development — and benefits directly from every compiler improvement.
+
## Tooling
### VS Code Extension
@@ -381,6 +412,9 @@ docker/ Dockerfile for build environment
## Documentation
+### Official Website
+- **[jdalang.org](https://www.jdalang.org)** — Official Jda language website, documentation, and resources.
+
### Getting Started
- [Build a CLI Tool](docs/getting-started/cli-tool.md) — word-count tool with args, file I/O
- [Build an HTTP Server](docs/getting-started/http-server.md) — static file server with raw syscalls
diff --git a/benchmarks/complex/btree/btree.jda b/benchmarks/complex/btree/btree.jda
index 82df533..b116eb9 100644
--- a/benchmarks/complex/btree/btree.jda
+++ b/benchmarks/complex/btree/btree.jda
@@ -3,9 +3,9 @@ let g_nn = 0
let g_root = 0
fn now_ms(ts: &i64) -> i64 {
- syscall(228, 1, ts, 0, 0)
- let s = ts[0]; let ns = ts[1]
- ret s * 1000 + ns / 1000000
+ syscall(96, ts, 0, 0, 0)
+ let s = ts[0]; let us = ts[1]
+ ret s * 1000 + us / 1000
}
// Order-32 B-tree: 31 keys, 32 children per node
diff --git a/benchmarks/complex/lz77/lz77.jda b/benchmarks/complex/lz77/lz77.jda
index d72da26..f57f87a 100644
--- a/benchmarks/complex/lz77/lz77.jda
+++ b/benchmarks/complex/lz77/lz77.jda
@@ -10,9 +10,9 @@ let g_aux: &i64
fn _sb(buf: &i8, idx: i64) -> i64 { ret buf[idx] }
fn now_ms(ts: &i64) -> i64 {
- syscall(228, 1, ts, 0, 0)
- let s = ts[0]; let ns = ts[1]
- ret s * 1000 + ns / 1000000
+ syscall(96, ts, 0, 0, 0)
+ let s = ts[0]; let us = ts[1]
+ ret s * 1000 + us / 1000
}
fn nrand() -> i64 {
diff --git a/benchmarks/complex/raytracer/raytracer.jda b/benchmarks/complex/raytracer/raytracer.jda
index 3d80335..c53fd50 100644
--- a/benchmarks/complex/raytracer/raytracer.jda
+++ b/benchmarks/complex/raytracer/raytracer.jda
@@ -8,9 +8,9 @@
let mem: &i64
fn now_ms(ts: &i64) -> i64 {
- syscall(228, 1, ts, 0, 0)
- let s = ts[0]; let ns = ts[1]
- ret s * 1000 + ns / 1000000
+ syscall(96, ts, 0, 0, 0)
+ let s = ts[0]; let us = ts[1]
+ ret s * 1000 + us / 1000
}
// Sphere data: 8 fields per sphere (cx,cy,cz,r, cr,cg,cb,spec) as i64 * 10000
@@ -48,7 +48,8 @@ fn intersect(ox: f64, oy: f64, oz: f64, dx: f64, dy: f64, dz: f64) -> f64 {
let dot_ococ: f64 = ocx * ocx + ocy * ocy + ocz * ocz
let rr: f64 = rad * rad
let disc: f64 = dot_ocd * dot_ocd - dot_ococ + rr
- let big: f64 = 100000000000000000.0
+ let big_b: f64 = 100000000.0
+ let big: f64 = big_b * 1000000000.0
if disc < 0.0 { ret big }
let sq: f64 = f64_sqrt(disc)
let neg_dot: f64 = f64_neg(dot_ocd)
@@ -60,7 +61,8 @@ fn intersect(ox: f64, oy: f64, oz: f64, dx: f64, dy: f64, dz: f64) -> f64 {
}
fn find_hit(ox: f64, oy: f64, oz: f64, dx: f64, dy: f64, dz: f64) -> i64 {
- let big: f64 = 100000000000000000.0
+ let big_b: f64 = 100000000.0
+ let big: f64 = big_b * 1000000000.0
let closest: f64 = big; let hit: i64 = 0 - 1; let i: i64 = 0
loop i < 5 {
g_si = i
@@ -78,7 +80,8 @@ fn shadow_check(px: f64, py: f64, pz: f64, lx: f64, ly: f64, lz: f64) -> i64 {
if si != g_skip {
g_si = si
let st: f64 = intersect(px, py, pz, lx, ly, lz)
- if st < 10000000000000000.0 { ret 1 }
+ let big_c: f64 = 100000000.0
+ if st < big_c * big_c { ret 1 }
}
si = si + 1
}
diff --git a/benchmarks/complex/regex/regex.jda b/benchmarks/complex/regex/regex.jda
index 9492c24..2e47158 100644
--- a/benchmarks/complex/regex/regex.jda
+++ b/benchmarks/complex/regex/regex.jda
@@ -32,9 +32,9 @@ let g_dfa_n = 0
fn _sb(buf: &i8, idx: i64) -> i64 { ret buf[idx] }
fn now_ms(ts: &i64) -> i64 {
- syscall(228, 1, ts, 0, 0)
- let s = ts[0]; let ns = ts[1]
- ret s * 1000 + ns / 1000000
+ syscall(96, ts, 0, 0, 0)
+ let s = ts[0]; let us = ts[1]
+ ret s * 1000 + us / 1000
}
fn st_sc(i: i64, v: i64) { mem[i * 3] = v }
@@ -234,13 +234,15 @@ fn match_nfa(str: &i8, slen: i64) -> i64 {
ret 0
}
+fn gen_str_wb(i: i64, c: i64) { poke_byte(g_strbuf, i, c) }
+
fn gen_str(seed: i64, slen: i64) {
let rng = (seed * 2654435761 + 1) & 2147483647
let seed_add = 12345 + seed
let i = 0
loop i < slen {
rng = (rng * 1103515245 + seed_add) & 2147483647
- poke_byte(g_strbuf, i, 97 + rng % 26)
+ gen_str_wb(i, 97 + rng % 26)
i = i + 1
}
}
diff --git a/benchmarks/complex/sudoku/sudoku.jda b/benchmarks/complex/sudoku/sudoku.jda
index 7aa1e7b..f3e32c4 100644
--- a/benchmarks/complex/sudoku/sudoku.jda
+++ b/benchmarks/complex/sudoku/sudoku.jda
@@ -8,9 +8,9 @@ fn _sb(buf: &i8, idx: i64) -> i64 { ret buf[idx] }
fn _rd(buf: &i8, n: i64) -> i64 { ret syscall(0, 0, buf, n, 0) }
fn now_ms(ts: &i64) -> i64 {
- syscall(228, 1, ts, 0, 0)
- let s = ts[0]; let ns = ts[1]
- ret s * 1000 + ns / 1000000
+ syscall(96, ts, 0, 0, 0)
+ let s = ts[0]; let us = ts[1]
+ ret s * 1000 + us / 1000
}
// Memory layout (i64 slots):
diff --git a/bootstrap/stage0/jda1 b/bootstrap/stage0/jda1
index 076128c..b107c39 100755
Binary files a/bootstrap/stage0/jda1 and b/bootstrap/stage0/jda1 differ
diff --git a/bootstrap/stage1/jda1.jda b/bootstrap/stage1/jda1.jda
index fe8ef26..241ddb2 100644
--- a/bootstrap/stage1/jda1.jda
+++ b/bootstrap/stage1/jda1.jda
@@ -611,6 +611,12 @@ let g_promo_ro_mask = 0
let g_save_mask: i64 = 0
// Copy propagation globals
let g_cp_slot_val: &i64 = 0
+let g_cp_no_init: i64 = 0
+// Global copy-prop dataflow tables
+let g_rd_blk_gen: &i64 = 0 // [256*128] GEN per block: -1=untouched, -2=killed, >=0=val_id
+let g_rd_blk_out: &i64 = 0 // [256*128] OUT from iterative dataflow
+let g_rd_pred: &i64 = 0 // [256*8] predecessor block ids
+let g_rd_pred_cnt: &i64 = 0 // [256] predecessor counts
// Register coalescing: old/new value IDs for substitution (set before coal_sub_* calls)
let g_coal_o: i64 = 0
let g_coal_n: i64 = 0
@@ -655,6 +661,8 @@ let g_skip_fn_body_compile_tmp = 0
let g_small_work_jfn: &JirFunction = 0
let g_small_work_bb = 0
let g_target_macos = 0
+let g_mac_text_pages: i64 = 0
+let g_mac_data_pages: i64 = 0
let g_fn_name_off_tbl: &i64 = 0
let g_fn_name_len_tbl: &i64 = 0
let g_fn_code_off_tbl: &i64 = 0
@@ -10029,7 +10037,7 @@ fn codegen_syscall_inline(toks: &Token, pos: &i64, stab: &StructTable, src: &i8,
let count = 0
let idx = 0
loop idx < 7 { g_sc_extra[idx] = -1 idx = idx + 1 }
- g_sc_extra[3] = nr
+ g_sc_extra[3] = macos_xlat_nr_id(jfn, bb, nr)
loop tok_type_at(toks, pos[0]) == TOK_COMMA {
pos[0] = pos[0] + 1
let arg = codegen_arg_inline(toks, pos, stab, src, jfn, bb)
@@ -13455,6 +13463,7 @@ fn cp_init_slots() {
fn cp_scan_block(jfn: &JirFunction, bi: i64) {
cp_init_slots()
+ rd_seed_in_bi(jfn, bi)
let base_id = -1
if jfn.blocks[bi].instr_cnt > 0 {
base_id = jfn.blocks[bi].instrs[0].id
@@ -13765,6 +13774,190 @@ fn coalesce_copies_block(jfn: &JirFunction, bi: i64) {
}
}
+// ============================================================
+// 7a-GLOBAL. REACHING-DEFINITIONS DATAFLOW
+// ============================================================
+// Task A: compute gen/kill bitvectors and iterate to fixed point.
+// g_rd_blk_gen[bi*128+si]: last effect on slot (512+si) in block bi
+// -1 = untouched, -2 = killed (OP_ADDR), >=0 = stored val_id
+// g_rd_blk_out[bi*128+si]: OUT state from fixed-point iteration
+// si = slot_idx - 512 (covers stack offsets 4096..5112)
+
+fn rd_add_pred(succ: i64, pred: i64) {
+ let cnt = g_rd_pred_cnt[succ]
+ if cnt < 8 {
+ g_rd_pred[succ * 8 + cnt] = pred
+ g_rd_pred_cnt[succ] = cnt + 1
+ }
+}
+
+fn rd_compute_preds(jfn: &JirFunction) {
+ let bi = 0
+ loop bi < 256 { g_rd_pred_cnt[bi] = 0; bi = bi + 1 }
+ bi = 0
+ loop bi < jfn.block_cnt {
+ let icnt = jfn.blocks[bi].instr_cnt
+ if icnt > 0 {
+ let lop = jfn.blocks[bi].instrs[icnt - 1].op
+ let t0 = jfn.blocks[bi].instrs[icnt - 1].bb_target0
+ let t1 = jfn.blocks[bi].instrs[icnt - 1].bb_target1
+ if lop == OP_BR {
+ if t0 >= 0 and t0 < 256 { rd_add_pred(t0, bi) }
+ if t1 >= 0 and t1 < 256 { rd_add_pred(t1, bi) }
+ }
+ if lop == OP_JMP {
+ if t0 >= 0 and t0 < 256 { rd_add_pred(t0, bi) }
+ }
+ }
+ bi = bi + 1
+ }
+}
+
+// Returns 1 if op is a float-producing or call instruction whose result lives
+// in an XMM register that emit_save_pool does NOT preserve across CALLs.
+fn rd_is_xmm_op(op: i64) -> i64 {
+ if op == OP_CALL or op == OP_CALL_IND or op == OP_SYSCALL { ret 1 }
+ if op == OP_FADD or op == OP_FSUB or op == OP_FMUL or op == OP_FDIV { ret 1 }
+ if op == OP_FNEG or op == OP_FSQRT or op == OP_FABS or op == OP_I2F { ret 1 }
+ if op == OP_FEXP or op == OP_FLOG or op == OP_FSIN or op == OP_FCOS { ret 1 }
+ if op == OP_FTANH or op == OP_FPOW or op == OP_F2I { ret 1 }
+ if op == OP_F64X2_BIN or op == OP_F64X4_BIN or op == OP_F64X8_BIN { ret 1 }
+ ret 0
+}
+
+// Returns 1 if val_id is the result of a call or float op (unsafe to propagate across calls).
+fn rd_val_is_xmm(jfn: &JirFunction, val_id: i64) -> i64 {
+ if val_id < 0 { ret 1 }
+ let b2 = 0
+ loop b2 < jfn.block_cnt {
+ let i2 = 0
+ loop i2 < jfn.blocks[b2].instr_cnt {
+ if jfn.blocks[b2].instrs[i2].id == val_id {
+ ret rd_is_xmm_op(jfn.blocks[b2].instrs[i2].op)
+ }
+ i2 = i2 + 1
+ }
+ b2 = b2 + 1
+ }
+ ret 1
+}
+
+fn rd_compute_gen(jfn: &JirFunction) {
+ let bi = 0
+ loop bi < jfn.block_cnt {
+ let base = bi * 128
+ let si = 0
+ loop si < 128 { g_rd_blk_gen[base + si] = -1; si = si + 1 }
+ let ii = 0
+ loop ii < jfn.blocks[bi].instr_cnt {
+ let op = jfn.blocks[bi].instrs[ii].op
+ let dead = jfn.blocks[bi].instrs[ii].dead
+ if dead == 0 and op == OP_TGET {
+ let kg = 0
+ loop kg < 128 { g_rd_blk_gen[base + kg] = -2; kg = kg + 1 }
+ }
+ if dead == 0 and op == OP_STORE {
+ let si2 = jfn.blocks[bi].instrs[ii].operand1 / 8 - 512
+ let val0 = jfn.blocks[bi].instrs[ii].operand0
+ if si2 >= 0 and si2 < 128 {
+ if rd_val_is_xmm(jfn, val0) == 0 {
+ g_rd_blk_gen[base + si2] = val0
+ }
+ }
+ }
+ if dead == 0 and op == OP_ADDR {
+ let si2 = jfn.blocks[bi].instrs[ii].operand0 / 8 - 512
+ if si2 >= 0 and si2 < 128 { g_rd_blk_gen[base + si2] = -2 }
+ }
+ ii = ii + 1
+ }
+ bi = bi + 1
+ }
+}
+
+fn rd_intersect_preds(bi: i64, si: i64) -> i64 {
+ let pcnt = g_rd_pred_cnt[bi]
+ if pcnt == 0 { ret -1 }
+ let agreed = -1
+ let bad = 0
+ let pi = 0
+ loop pi < pcnt {
+ let pred = g_rd_pred[bi * 8 + pi]
+ let pv = g_rd_blk_out[pred * 128 + si]
+ if pv < 0 { bad = 1 }
+ if agreed < 0 { agreed = pv }
+ if pv != agreed { bad = 1 }
+ pi = pi + 1
+ }
+ if bad == 1 { ret -1 }
+ ret agreed
+}
+
+fn rd_compute_out_bi(bi: i64) -> i64 {
+ let base = bi * 128
+ let changed = 0
+ let si = 0
+ loop si < 128 {
+ let gen_v = g_rd_blk_gen[base + si]
+ let new_out = -1
+ if gen_v >= 0 { new_out = gen_v }
+ if gen_v == -1 { new_out = rd_intersect_preds(bi, si) }
+ if new_out != g_rd_blk_out[base + si] {
+ g_rd_blk_out[base + si] = new_out
+ changed = 1
+ }
+ si = si + 1
+ }
+ ret changed
+}
+
+fn rd_run_dataflow(jfn: &JirFunction) {
+ let bi = 0
+ loop bi < jfn.block_cnt {
+ let si = 0
+ loop si < 128 { g_rd_blk_out[bi * 128 + si] = -1; si = si + 1 }
+ bi = bi + 1
+ }
+ let itr = 0
+ let chg = 1
+ loop chg == 1 and itr < 8 {
+ chg = 0
+ bi = 0
+ loop bi < jfn.block_cnt {
+ let c = rd_compute_out_bi(bi)
+ if c == 1 { chg = 1 }
+ bi = bi + 1
+ }
+ itr = itr + 1
+ }
+}
+
+fn rd_seed_in_bi(jfn: &JirFunction, bi: i64) {
+ let si = 0
+ loop si < 128 {
+ let in_v = rd_intersect_preds(bi, si)
+ if in_v >= 0 {
+ let cv = find_const(jfn, in_v)
+ if cv.found == 1 {
+ let idx = si + 512
+ g_cp_slot_val[idx] = in_v
+ }
+ }
+ si = si + 1
+ }
+}
+
+// Task B+C: global copy-prop — seed each block's initial slot→val map from
+// reaching definitions before running intra-block cp_scan_block.
+// Only constants are seeded cross-block; non-const values may be in caller-saved
+// registers that get clobbered by calls in predecessor blocks.
+fn copy_prop_global(jfn: &JirFunction) {
+ rd_compute_preds(jfn)
+ rd_compute_gen(jfn)
+ rd_run_dataflow(jfn)
+ copy_prop(jfn)
+}
+
fn copy_prop(jfn: &JirFunction) {
let bi = 0
loop bi < jfn.block_cnt {
@@ -15274,7 +15467,6 @@ fn loop_promote_scan(jfn: &JirFunction) {
let cnl = jfn.blocks[lb].instrs[li].str_len
let is_safe = 0
if cnl == 3 { if streq(g_src_buf_ptr, cns, 3, "_sb") == 1 { is_safe = 1 } }
- if cnl == 9 { if streq(g_src_buf_ptr, cns, 9, "poke_byte") == 1 { is_safe = 1 } }
if is_safe == 0 { has_call = 1 }
}
if lop == OP_CALL_IND { has_call = 1 }
@@ -17086,7 +17278,7 @@ fn lower_syscall_consume_arg(ins: &Instr, ctx: &LowerCtx, idx: i64) {
fn lower_syscall(ins: &Instr, ctx: &LowerCtx, out: &i8, pos: &i64) {
emit_push_r(out, pos, PHYS_R12)
emit_save_pool(ctx.ra, out, pos)
-
+
// Push in order (always at least the nr)
let nr_r = get_or_load(ctx.ra, out, pos, ins.operand0)
emit_push_r(out, pos, nr_r)
@@ -25481,6 +25673,35 @@ fn nop_fallthrough_jmps(out: &i8, start: i64, end_pos: i64) {
}
}
+fn macos_xlat_sc_lo(nr: i64) -> i64 {
+ if nr == 0 { ret 0x2000003 } // read
+ if nr == 1 { ret 0x2000004 } // write
+ if nr == 2 { ret 0x2000005 } // open
+ if nr == 3 { ret 0x2000006 } // close
+ if nr == 9 { ret 0x20000C5 } // mmap
+ ret 0
+}
+
+fn macos_xlat_syscall(nr: i64) -> i64 {
+ let lo = macos_xlat_sc_lo(nr)
+ if lo != 0 { ret lo }
+ if nr == 11 { ret 0x2000049 } // munmap
+ if nr == 60 { ret 0x2000001 } // exit
+ if nr == 96 { ret 0x2000074 } // gettimeofday
+ if nr == 231 { ret 0x2000001 } // exit_group
+ ret nr
+}
+
+fn macos_xlat_nr_id(jfn: &JirFunction, bb: i64, nr_id: i64) -> i64 {
+ if g_target_macos == 0 { ret nr_id }
+ let cv = ConstVal{}
+ cv = find_const(jfn, nr_id)
+ if cv.found == 0 { ret nr_id }
+ let mac_nr = macos_xlat_syscall(cv.val)
+ if mac_nr == cv.val { ret nr_id }
+ ret emit_const(jfn, bb, TYPE_I64, mac_nr)
+}
+
fn p_sys_mmap() -> i64 {
if g_target_macos == 1 { ret 0x20000C5 }
ret 9
@@ -25527,7 +25748,7 @@ fn p_load_va() -> i64 {
}
fn p_hdr_size() -> i64 {
- if g_target_macos == 1 { ret 560 }
+ if g_target_macos == 1 { ret 712 } // 560 + 152 for __DATA LC_SEGMENT_64
ret 120
}
@@ -25627,15 +25848,42 @@ fn write_macho_seg_link(buf: &i8, pos: &i64, va: i64, vmsz: i64, foff: i64, fsz:
emit_i32_le(buf, pos, 0)
}
+fn write_macho_data_seg(buf: &i8, pos: &i64, va: i64, vmsz: i64, foff: i64) {
+ // LC_SEGMENT_64 __DATA with 1 section (cmdsize=152), initprot=3 (r+w)
+ emit_i32_le(buf, pos, 0x19)
+ emit_i32_le(buf, pos, 152)
+ write_macho_segname(buf, pos, "__DATA")
+ emit_i64_le(buf, pos, va)
+ emit_i64_le(buf, pos, vmsz)
+ emit_i64_le(buf, pos, foff)
+ emit_i64_le(buf, pos, vmsz)
+ emit_i32_le(buf, pos, 3)
+ emit_i32_le(buf, pos, 3)
+ emit_i32_le(buf, pos, 1)
+ emit_i32_le(buf, pos, 0)
+ write_macho_segname(buf, pos, "__data")
+ write_macho_segname(buf, pos, "__DATA")
+ emit_i64_le(buf, pos, va)
+ emit_i64_le(buf, pos, vmsz)
+ emit_i32_le(buf, pos, foff)
+ emit_i32_le(buf, pos, 0)
+ emit_i32_le(buf, pos, 0)
+ emit_i32_le(buf, pos, 0)
+ emit_i32_le(buf, pos, 0)
+ emit_i32_le(buf, pos, 0)
+ emit_i32_le(buf, pos, 0)
+ emit_i32_le(buf, pos, 0)
+}
+
fn write_macho_text_seg(hdr: &i8, hp: &i64, link_off: i64, clen: i64, hdr_sz: i64) {
// LC_SEGMENT_64 with 1 section: cmdsize = 72 + 80 = 152
emit_i32_le(hdr, hp, 0x19)
emit_i32_le(hdr, hp, 152)
write_macho_segname(hdr, hp, "__TEXT")
emit_i64_le(hdr, hp, 0x100000000)
- emit_i64_le(hdr, hp, link_off)
+ emit_i64_le(hdr, hp, g_mac_text_pages)
emit_i64_le(hdr, hp, 0)
- emit_i64_le(hdr, hp, link_off)
+ emit_i64_le(hdr, hp, g_mac_text_pages)
emit_i32_le(hdr, hp, 5)
emit_i32_le(hdr, hp, 5)
emit_i32_le(hdr, hp, 1)
@@ -25673,7 +25921,7 @@ fn write_macho_thread_state(buf: &i8, pos: &i64) {
loop i < 42 {
if i == 32 {
// rip = base_va + header_size = 0x100000000 + 560 = 0x100000230
- emit_i32_le(buf, pos, 0x230)
+ emit_i32_le(buf, pos, 0x2C8)
emit_i32_le(buf, pos, 0x1)
i = i + 2
} else {
@@ -25683,24 +25931,35 @@ fn write_macho_thread_state(buf: &i8, pos: &i64) {
}
}
-fn write_macho_body(fd: i64, code: &i8, clen: i64, strtab: &i8, slen: i64, pad_total: i64) {
+fn write_macho_body(fd: i64, code: &i8, clen: i64, strtab: &i8, slen: i64, text_pages: i64) {
+ let zero_blk = alloc_pages(2)
+ // Write code, then pad to text_pages boundary
syscall(1, fd, code, clen)
+ let hdr_sz = p_hdr_size()
+ let code_pad = text_pages - hdr_sz
+ code_pad = code_pad - clen
+ fill_bytes(zero_blk, code_pad, 0)
+ syscall(1, fd, zero_blk, code_pad)
+ // Write strtab + globals into __DATA
syscall(1, fd, strtab, slen)
if g_emit_glob_len > 0 {
syscall(1, fd, g_emit_glob_buf, g_emit_glob_len)
}
- let zero_blk = alloc_pages(1)
- fill_bytes(zero_blk, pad_total, 0)
- syscall(1, fd, zero_blk, pad_total)
+ // Pad data section to data_pages, then write LINKEDIT (16 bytes)
+ let data_len = slen + g_emit_glob_len
+ let data_pad = g_mac_data_pages - data_len
+ let link_pad = data_pad + 16
+ fill_bytes(zero_blk, link_pad, 0)
+ syscall(1, fd, zero_blk, link_pad)
}
fn write_macho_header(hdr: &i8, hp: &i64, link_off: i64, sig_sz: i64, clen: i64, hdr_sz: i64) {
- // Mach-O header: 5 load commands (no LC_CODE_SIGNATURE — codesign adds it)
+ // Mach-O header: 6 load commands (no LC_CODE_SIGNATURE — codesign adds it)
emit_i32_le(hdr, hp, 0xFEEDFACF)
emit_i32_le(hdr, hp, 0x01000007)
emit_i32_le(hdr, hp, 3)
emit_i32_le(hdr, hp, 2)
- emit_i32_le(hdr, hp, 5)
+ emit_i32_le(hdr, hp, 6)
let soc_off = hp[0]
emit_i32_le(hdr, hp, 0)
emit_i32_le(hdr, hp, 0x00000001)
@@ -25709,6 +25968,9 @@ fn write_macho_header(hdr: &i8, hp: &i64, link_off: i64, sig_sz: i64, clen: i64,
write_macho_seg_pz(hdr, hp)
// LC_SEGMENT_64 __TEXT with 1 section (cmdsize=152)
write_macho_text_seg(hdr, hp, link_off, clen, hdr_sz)
+ // LC_SEGMENT_64 __DATA with 1 section (cmdsize=152), r+w
+ let data_va = 0x100000000 + g_mac_text_pages
+ write_macho_data_seg(hdr, hp, data_va, g_mac_data_pages, g_mac_text_pages)
// LC_SEGMENT_64 __LINKEDIT (cmdsize=72), page-aligned vmsize
let link_va = 0x100000000 + link_off
let link_vmsz = 0x1000
@@ -25731,20 +25993,23 @@ fn write_macho_header(hdr: &i8, hp: &i64, link_off: i64, sig_sz: i64, clen: i64,
}
fn write_macho(fd: i64, code: &i8, clen: i64, strtab: &i8, slen: i64) {
- let data_len = slen + g_emit_glob_len
- let content_sz = clen + data_len
let hdr_sz = p_hdr_size()
- let raw_end = hdr_sz + content_sz
- // Page-align __TEXT segment: vmsize and filesize = text_pages
- let text_pages = (raw_end + 0xFFF) and (0 - 0x1000)
- let text_pad = text_pages - raw_end
+ // __TEXT: header + code only (r+x)
+ let text_raw = hdr_sz + clen
+ let text_pages = (text_raw + 0xFFF) and (0 - 0x1000)
+ // __DATA: strtab + globals (r+w)
+ let data_len = slen + g_emit_glob_len
+ let data_pages = (data_len + 0xFFF) and (0 - 0x1000)
+ if data_len == 0 { data_pages = 0 }
+ g_mac_text_pages = text_pages
+ g_mac_data_pages = data_pages
+ let link_off = text_pages + data_pages
let link_sz = 16
- let pad_total = text_pad + link_sz
let hdr = alloc_pages(1)
let hp = 0
- write_macho_header(hdr, &hp, text_pages, link_sz, clen, hdr_sz)
+ write_macho_header(hdr, &hp, link_off, link_sz, clen, hdr_sz)
syscall(1, fd, hdr, hdr_sz)
- write_macho_body(fd, code, clen, strtab, slen, pad_total)
+ write_macho_body(fd, code, clen, strtab, slen, text_pages)
}
fn get_argv(base: &i64, n: i64) -> &i8 {
@@ -26196,9 +26461,9 @@ fn compile_lambda_params_and_body(toks: &Token, src: &i8, jfn: &JirFunction, ctx
ri.op = OP_RET
ri.operand0 = rv
emit(jfn, fb, &ri)
- copy_prop(jfn)
+ copy_prop_global(jfn)
fold_constants(jfn)
- copy_prop(jfn)
+ copy_prop_global(jfn)
fold_constants(jfn)
dead_store_elim(jfn)
dce(jfn)
@@ -26760,6 +27025,28 @@ fn defer_emit_calls(toks0: &Token, stab: &StructTable, src: &i8, jfn: &JirFuncti
}
}
+fn calc_str_va(fix_va: i64, fix_hdr: i64, code_len: i64) -> i64 {
+ let str_va = fix_va + fix_hdr + code_len
+ if g_target_macos == 1 {
+ let text_raw = fix_hdr + code_len
+ let text_pages_fix = (text_raw + 0xFFF) and (0 - 0x1000)
+ str_va = fix_va + text_pages_fix
+ }
+ ret str_va
+}
+
+fn do_emit_and_exit(code_buf: &i8, code_len: i64, merged_str: &i8, merged_pos: i64, glob_buf: &i8) {
+ let out_fd = syscall(2, g_out_path_ptr, 577, 493)
+ if out_fd < 0 { panic("open output failed") }
+ g_emit_glob_buf = glob_buf
+ g_emit_glob_len = g_glob_total_sz
+ if g_glob_total_sz > 0 { fill_bytes(glob_buf, g_glob_total_sz, 0) }
+ emit_output(out_fd, code_buf, code_len, merged_str, merged_pos)
+ syscall(3, out_fd, 0, 0)
+ syscall(90, g_out_path_ptr, 493, 0)
+ syscall(60, 0, 0, 0)
+}
+
fn main(argc: i64, argv_ptr: &i64) {
let src_path: &i8 = 0
let out_path: &i8 = 0
@@ -27073,6 +27360,10 @@ fn main(argc: i64, argv_ptr: &i64) {
ls_init_globals()
// Copy propagation arrays
g_cp_slot_val = alloc_pages(4) as &i64
+ g_rd_blk_gen = alloc_pages(64) as &i64
+ g_rd_blk_out = alloc_pages(64) as &i64
+ g_rd_pred = alloc_pages(16) as &i64
+ g_rd_pred_cnt = alloc_pages(1) as &i64
g_block_indeg = alloc_pages(1) as &i64
// Bootstrap: alloc_pages gives zero-initialized memory so entry 0 is already zero.
// Just set the count and total size to skip slot 0 (dummy entry).
@@ -27217,9 +27508,9 @@ fn main(argc: i64, argv_ptr: &i64) {
if jfn.block_cnt > 512 { panic("lex_handle_minus block blowup") }
}
}
- // copy_prop(jfn) -- BASELINE
+ // copy_prop_global(jfn) -- BASELINE
fold_constants(jfn)
- // copy_prop(jfn) -- BASELINE
+ // copy_prop_global(jfn) -- BASELINE
fold_constants(jfn)
dead_store_elim(jfn)
dce(jfn)
@@ -27315,7 +27606,7 @@ fn main(argc: i64, argv_ptr: &i64) {
// ---- Global Fixups (String/Calls) ----
let fix_va = p_load_va()
let fix_hdr = p_hdr_size()
- let str_va = fix_va + fix_hdr + code_len
+ let str_va = calc_str_va(fix_va, fix_hdr, code_len)
let si_f = 0
loop si_f < gf_strtab_cnt {
let co = gf_strtab_co[si_f]
@@ -27378,16 +27669,5 @@ fn main(argc: i64, argv_ptr: &i64) {
}
ci_f = ci_f + 1
}
- let out_fd = syscall(2, g_out_path_ptr, 577, 493)
- if out_fd < 0 { panic("open output failed") }
- g_emit_glob_buf = glob_buf
- g_emit_glob_len = g_glob_total_sz
- if g_glob_total_sz > 0 { fill_bytes(glob_buf, g_glob_total_sz, 0) }
- emit_output(out_fd, code_buf, code_len, merged_str, merged_pos)
- syscall(3, out_fd, 0, 0)
-
- // chmod +x the output
- syscall(90, g_out_path_ptr, 493, 0)
-
- syscall(60, 0, 0, 0)
+ do_emit_and_exit(code_buf, code_len, merged_str, merged_pos, glob_buf)
}
diff --git a/tools/jda-macos.sh b/tools/jda-macos.sh
index 1782a34..d61670b 100755
--- a/tools/jda-macos.sh
+++ b/tools/jda-macos.sh
@@ -365,6 +365,7 @@ function prescan( saved_pos, sname, fname_f, offset, depth, gname, \
else if (et == "i32" || et == "u32") field_sz = ne * 4
else if (et == "i16" || et == "u16") field_sz = ne * 2
else field_sz = ne * 8
+ struct_field_is_array[sname ":" fname_f] = 1
} else if (peek_kind() == "id" && tk_kind[POS+1] == "]") {
cn = peek_val(); advance()
if (peek_kind() == "]") advance()
@@ -376,6 +377,7 @@ function prescan( saved_pos, sname, fname_f, offset, depth, gname, \
else if (et == "i32" || et == "u32") field_sz = ne * 4
else if (et == "i16" || et == "u16") field_sz = ne * 2
else field_sz = ne * 8
+ struct_field_is_array[sname ":" fname_f] = 1
} else {
# []type = pointer/slice = 8 bytes
if (peek_kind() == "]") advance()
@@ -749,11 +751,27 @@ function arm64_parse_postfix( fname_f, foff, nargs, saved_lt) {
emit(" bl _strlen")
LAST_TYPE = "i64"
} else {
- # Field read
- foff = find_field_off(fname_f, LAST_TYPE)
+ # Field read — inline array fields yield the field address, not a loaded pointer
+ _flt = LAST_TYPE
+ foff = find_field_off(fname_f, _flt)
+ _fkey = (_flt != "" && (_flt ":" fname_f) in struct_field_is_array) ? (_flt ":" fname_f) : \
+ (fname_f in struct_field_is_array ? fname_f : "")
+ _is_arr = (_flt ":" fname_f) in struct_field_is_array
+ if (!_is_arr) {
+ for (_asn in struct_names) {
+ if ((_asn ":" fname_f) in struct_field_is_array) { _is_arr = 1; break }
+ }
+ }
if (foff >= 0) {
- if (foff <= 32760) emit(" ldr x0, [x0, #" foff "]")
- else { emit(" mov x9, #" foff); emit(" ldr x0, [x0, x9]") }
+ if (_is_arr) {
+ # Inline array field: result is address of field, not a load
+ if (foff == 0) { /* x0 already points to the field */ }
+ else if (foff <= 4095) emit(" add x0, x0, #" foff)
+ else { emit(" mov x9, #" foff); emit(" add x0, x0, x9") }
+ } else {
+ if (foff <= 32760) emit(" ldr x0, [x0, #" foff "]")
+ else { emit(" mov x9, #" foff); emit(" ldr x0, [x0, x9]") }
+ }
}
LAST_TYPE = ""
}
@@ -761,6 +779,7 @@ function arm64_parse_postfix( fname_f, foff, nargs, saved_lt) {
} else if (peek_kind() == "[") {
advance() # [
if (peek_kind() == "]") { advance(); LAST_TYPE = "ptr"; break }
+ saved_lt = LAST_TYPE # save element type before index eval clobbers it
emit(" str x0, [sp, #-16]!") # save base
arm64_parse_expr() # idx or start in x0
if (peek_kind() == "." && tk_kind[POS+1] == ".") {
@@ -780,8 +799,13 @@ function arm64_parse_postfix( fname_f, foff, nargs, saved_lt) {
} else {
emit(" mov x1, x0") # index
emit(" ldr x0, [sp], #16") # base
- emit(" lsl x1, x1, #3") # scale by 8 for i64
- emit(" ldr x0, [x0, x1]") # load i64
+ if (saved_lt == "i8" || saved_lt == "u8" || saved_lt == "str") {
+ emit(" add x0, x0, x1") # byte address: base + idx
+ emit(" ldrb w0, [x0]") # 1-byte load, zero-extended
+ } else {
+ emit(" lsl x1, x1, #3") # scale by 8 for i64/ptr
+ emit(" ldr x0, [x0, x1]") # 8-byte load
+ }
}
expect("]")
LAST_TYPE = ""
@@ -1017,7 +1041,7 @@ function arm64_parse_primary( kind, val, name, nargs, idx, lo, hi, sname, bas
LAST_TYPE = var_type[name]
emit(" ldr x0, [x29, #" env[name] "]")
} else if (name in global_off) {
- LAST_TYPE = ""
+ LAST_TYPE = (name in var_type) ? var_type[name] : ""
emit(" adrp x9, _jda_globals@PAGE")
emit(" add x9, x9, _jda_globals@PAGEOFF")
emit(" ldr x0, [x9, #" global_off[name] "]")
@@ -1430,8 +1454,11 @@ function arm64_gen_stmt( kind, val, name, off, else_lbl, end_lbl, top_lbl, co
# Continue to next field or =
} else if (peek_kind() == "=") {
# Final: .field = expr
+ # Save x9 (struct pointer) — expr eval may clobber it via adrp x9 for globals
advance() # =
+ emit(" str x9, [sp, #-16]!")
arm64_parse_expr()
+ emit(" ldr x9, [sp], #16")
if (foff >= 0) {
if (foff <= 32760) emit(" str x0, [x9, #" foff "]")
else { emit(" mov x10, #" foff); emit(" str x0, [x9, x10]") }
@@ -1501,8 +1528,14 @@ function arm64_gen_stmt( kind, val, name, off, else_lbl, end_lbl, top_lbl, co
arm64_parse_expr() # RHS → x0
emit(" ldr x9, [sp], #16") # restore ptr into x9
emit(" ldr x10, [sp], #16") # restore idx into x10
- emit(" lsl x10, x10, #3") # scale index by 8 for i64
- emit(" str x0, [x9, x10]") # store i64
+ _elem_type = (name in var_type) ? var_type[name] : ""
+ if (_elem_type == "i8" || _elem_type == "u8" || _elem_type == "str") {
+ emit(" add x9, x9, x10") # byte address: base + idx
+ emit(" strb w0, [x9]") # 1-byte store
+ } else {
+ emit(" lsl x10, x10, #3") # scale index by 8 for i64/ptr
+ emit(" str x0, [x9, x10]") # 8-byte store
+ }
return
}
# Not an assignment: discard pushed idx and fall through