|
| 1 | +# Architecture |
| 2 | + |
| 3 | +## Compiler Pipeline |
| 4 | + |
| 5 | +JavaScriptTarget.jl uses Julia's built-in type inference and compilation pipeline to produce JavaScript. |
| 6 | + |
| 7 | +``` |
| 8 | +Julia source → Julia compiler → Typed IR (CodeInfo) → JavaScriptTarget codegen → JavaScript |
| 9 | +``` |
| 10 | + |
| 11 | +### Step 1: Type Inference |
| 12 | + |
| 13 | +Julia's compiler infers types for all variables given concrete argument types. `compile(f, (Int32, Int32))` triggers `Base.code_typed(f, (Int32, Int32))` to get fully typed intermediate representation. |
| 14 | + |
| 15 | +### Step 2: IR Walking |
| 16 | + |
| 17 | +The typed IR (`CodeInfo`) contains SSA (Static Single Assignment) statements. Each statement is one of: |
| 18 | + |
| 19 | +| IR Node | JavaScript | |
| 20 | +|---|---| |
| 21 | +| `ReturnNode(val)` | `return val;` | |
| 22 | +| `GotoIfNot(cond, dest)` | `if (!cond) { ... }` | |
| 23 | +| `GotoNode(label)` | `while`/`break`/`continue` | |
| 24 | +| `PhiNode(edges, values)` | `let var; ... var = val;` | |
| 25 | +| `PiNode(val, type)` | No-op (type narrowing) | |
| 26 | +| `Expr(:call, f, args...)` | Intrinsic or function call | |
| 27 | +| `Expr(:invoke, mi, args...)` | Direct function call | |
| 28 | +| `Expr(:new, T, args...)` | `new ClassName(args)` | |
| 29 | + |
| 30 | +### Step 3: Type-Directed Emission |
| 31 | + |
| 32 | +The inferred types control how operations are emitted: |
| 33 | + |
| 34 | +- **Int32**: All arithmetic gets `| 0` coercion. Multiplication uses `Math.imul()`. |
| 35 | +- **Float64**: Standard JS number operations, no coercion. |
| 36 | +- **String**: Direct JS string. Interpolation becomes template literals. |
| 37 | +- **Struct**: ES6 class with `$type` on prototype for dispatch. |
| 38 | +- **Nothing**: `null`. **Missing**: `undefined`. |
| 39 | +- **Vector**: Compile-time index offset (`a[i]` → `a[i-1]`). |
| 40 | + |
| 41 | +### Step 4: Output |
| 42 | + |
| 43 | +The codegen produces a `JSOutput` struct containing: |
| 44 | +- `.js` — Generated JavaScript |
| 45 | +- `.dts` — TypeScript definitions |
| 46 | +- `.sourcemap` — V3 source map (if enabled) |
| 47 | +- `.exports` — List of exported names |
| 48 | +- `.runtime_bytes` — Size of included runtime helpers |
| 49 | + |
| 50 | +## Self-Hosted Browser Playground |
| 51 | + |
| 52 | +The playground runs Julia entirely in the browser using a custom compilation pipeline written in JavaScript: |
| 53 | + |
| 54 | +``` |
| 55 | +Julia Source |
| 56 | + ↓ |
| 57 | + parser.js — Recursive descent parser (tokenizer + Pratt precedence) |
| 58 | + ↓ |
| 59 | + lowerer.js — AST → SSA IR with phi nodes |
| 60 | + ↓ |
| 61 | + infer.js — Forward SSA type inference (3-tier lookup) |
| 62 | + ↓ |
| 63 | + codegen.js — SSA IR + types → JavaScript |
| 64 | + ↓ |
| 65 | + eval() — Execute in browser |
| 66 | +``` |
| 67 | + |
| 68 | +### Pipeline Components |
| 69 | + |
| 70 | +**parser.js** (~1,700 lines) — Hand-written tokenizer and recursive descent parser. Supports all Julia literals, 16 operator precedence levels, function definitions, structs, control flow, lambdas, string interpolation, and type annotations. |
| 71 | + |
| 72 | +**lowerer.js** (~2,075 lines) — Transforms AST into SSA IR. Performs scope analysis, control flow construction with phi nodes, and 15 macro pre-expansions (@show, @assert, etc.). |
| 73 | + |
| 74 | +**infer.js** (~790 lines) — Type inference using pre-computed tables from `types.bin`: |
| 75 | +1. FNV-1a hash table lookup for concrete types |
| 76 | +2. Parametric rule matching for generic patterns |
| 77 | +3. Fallback to `Any` |
| 78 | + |
| 79 | +Forward SSA pass with loop fixed-point iteration (max 4 iterations). |
| 80 | + |
| 81 | +**codegen.js** (~1,340 lines) — Generates JavaScript from SSA IR with type-directed emission. Structure hint analysis identifies if/while/for/try regions. Inline runtime helpers are tree-shakeable. |
| 82 | + |
| 83 | +**runtime.js** (~210 lines) — Output capture, string helpers, struct equality, math helpers, checked arithmetic, and execution sandbox. |
| 84 | + |
| 85 | +**worker.js** (~135 lines) — Web Worker orchestrating the pipeline. Receives Julia source, runs parse→lower→infer→codegen→eval, returns output. |
| 86 | + |
| 87 | +**types.bin** (~82 KB, ~10 KB gzipped) — Pre-computed inference tables generated by `build_inference_tables()`. Contains TypeID registry (55 types), FNV-1a hash table (670 entries), and 123 parametric rules. |
| 88 | + |
| 89 | +### Build Process |
| 90 | + |
| 91 | +```julia |
| 92 | +# Generate the complete playground |
| 93 | +build_playground("./output"; verbose=true, bundle=false) |
| 94 | +``` |
| 95 | + |
| 96 | +This copies all JS files, generates `types.bin` from Julia's type system, and includes the CodeMirror-based `index.html`. |
0 commit comments