@mica-js/compiler 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DigitalEcho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,276 @@
1
- # Temporary Holding Version
1
+ # @mica-js/compiler
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Browser-compatible TypeScript ESM compiler for validated `@mica-js/device-model`
4
+ execution bodies. All `OperationIR` variants are emitted as direct JavaScript;
5
+ there is no runtime IR dispatcher or dependency on the old IR or simulator.
6
+
7
+ ## API
8
+
9
+ - `compileBody(region)` returns `(host, args?, reaction?) => Generator`.
10
+ - `compileComputation(computation)` returns `(host) => unknown`. It synchronously
11
+ completes nested calls and rejects any yielded execution signal.
12
+ - `compileBodyArtifact` and `compileComputationArtifact` additionally return the
13
+ executable, generated JavaScript source, captured constants, operation/source
14
+ positions, suspension/resume locations and conservative effect metadata.
15
+ - `ExecutionHost`, `ExecutionSignal`, `ReactionFrame` and value helpers are exported.
16
+
17
+ Inputs must have passed model validation, scope/effect checks and instance binding.
18
+ The compiler does not replace model validation. Arbitrary names and literal values
19
+ are captured as data; binary64 constants retain NaN, infinities and negative zero.
20
+ Primitive constant pooling preserves signed zero, and known primitive equalities
21
+ use direct strict comparisons. No effectful computation is reordered or eliminated.
22
+ Generated bodies no longer reference their input IR.
23
+
24
+ `compileBody(fn.body)(host, args)` needs no function parameter declarations at
25
+ compile time. Each value parameter is initialized by its explicit
26
+ `{ kind: 'parameter', index, result, type }` operation, which reads `args[index]`.
27
+ Resource parameters instead use parameter references directly. Supply arguments in
28
+ the declared positional order, retaining resource parameters in their positions.
29
+ Argument count, resource category and value types are validated by the model/binder,
30
+ not inferred or defaulted by the compiler.
31
+
32
+ Native JavaScript generators preserve locals across suspension. Native branches,
33
+ loops and labeled value regions preserve lazy evaluation and control flow. Calls
34
+ use generator delegation; stop and redirect terminate the entire delegated path,
35
+ even if a driver resumes after a terminal signal. Drivers should close the outer
36
+ execution generator when receiving either terminal signal. Receive results are
37
+ passed to `generator.next(value)`. A receive or waitUntil signal can be satisfied
38
+ immediately by the driver without introducing a scheduling boundary. Ticks always
39
+ yield, including zero; elapse(0) does not yield. waitUntil ends its instruction path.
40
+ Time and redirect values are checked at the operation's execution position.
41
+
42
+ Runtime values use `null` for none, enum member-name strings, frozen plain records,
43
+ `Uint8Array` byte snapshots, plain sample objects and flat readonly intent branch
44
+ arrays. Both constructed and constant bytes produce a fresh `Uint8Array` each time
45
+ the operation executes. Byte constants capture frozen number-array seeds, never a
46
+ mutable buffer reused across invocations. Typed arrays are immutable by execution
47
+ contract: hosts must not mutate received values, must copy mutable boundary inputs
48
+ and must provide SDK `ReadonlyBytes` wrappers at the external boundary.
49
+ `host.resistor` supplies an intent branch array; intent combinations flatten these
50
+ arrays without changing branches. Resource references are direct name strings or
51
+ the corresponding `args[index]`. The host enforces memory, resistor, channel and
52
+ resource ownership contracts.
53
+ Equality is structural for records, bytes and samples, with strict numeric equality
54
+ including NaN inequality and signed-zero equality. Strings index UTF-16 code units.
55
+ Byte construction and indexing enforce range checks without coercion.
56
+
57
+ Effect metadata is syntactic and includes all branches. Calls are conservatively
58
+ marked as possibly reading/writing any resource, suspending, redirecting, stopping,
59
+ reading time and consuming random state; resolve actual transitive effects through
60
+ the validated model call graph. Position lines/columns refer to the exposed generated
61
+ source (one-based), not the engine's surrounding Function wrapper. Resume locations
62
+ are diagnostic metadata, not serializable generator frames.
63
+
64
+ Compilation uses `new Function` once per body and requires a host CSP that permits
65
+ dynamic JavaScript compilation. Generated execution itself requires no Node APIs.
66
+ No asynchronous execution, scheduling or electrical solving is implemented here.
67
+
68
+ ## Bounded Direct JIT
69
+
70
+ `compileDirectJit(instructions, binding)` optionally compiles an entire cached PC
71
+ trace into one numeric-bank switch/loop. Each instruction supplies `pc`, `nextPc`
72
+ and its already validated `body`. Unsupported instructions are omitted; an unknown
73
+ or unsupported PC exits with `fallback` before changing that instruction's PC or
74
+ performing any of its effects. The first occurrence of each PC owns lookup, even
75
+ when unsupported. If nothing is supported, compilation returns `undefined`.
76
+ Optional binding `blockedStates?: ReadonlySet<string>` and
77
+ `blockedMemories?: ReadonlySet<string>` are compile-time write barriers. A write to
78
+ any blocked name, including implicit PC assignment or a write in either `if`/`select`
79
+ branch, excludes that complete instruction before any effect or PC change. Other
80
+ cached PCs remain compilable, and excluded instructions contribute no resource
81
+ metadata. Reading a blocked resource is still supported. No barrier Set lookups
82
+ occur during execution. Recompile when membership changes; finish any old pending
83
+ instruction with `stopAfterInstruction` before switching programs.
84
+
85
+ `DirectJitBinding` supplies `states` and `memories` as `ReadonlyMap<string, number>`
86
+ and an explicit `pc: { name, index }` matching the state bank. `DirectJitFrame` is
87
+ runtime-owned reusable storage: `states: unknown[]`, `memories: Uint8Array[]`,
88
+ `stateDirty: number[]`, `memoryDirty: number[]`, `time`, `due`, `resume`,
89
+ `locals: unknown[]`, and optional `stopAfterInstruction`.
90
+ Initialize `resume` to zero and allocate `locals` with `program.localCount` slots.
91
+ Allocate dirty arrays with `Math.ceil(bank.length / 32)` words and clear both arrays
92
+ before each `run` invocation. Each completed write sets its resource's bit using
93
+ `dirty[index >>> 5] |= 1 << (index & 31)`, including implicit and redirected PC writes.
94
+ Bits are set after validation and the write; reads and failed writes do not set bits.
95
+ Nonzero resume IDs and saved locals belong to the producing program; never transfer
96
+ an outstanding continuation to a newly compiled program. `due` is an absolute wake
97
+ time. Times and durations use integer picoseconds in the existing time range.
98
+
99
+ `program.run(frame, deadline)` returns `deadline`, `fallback`, `stopped`, or
100
+ `invalidated`. It executes multiple instructions without generators, per-instruction
101
+ objects, host callbacks, or runtime Map lookups. Scalar intermediates stay in native
102
+ locals until a horizon requires saving them. State/memory effects accumulate in the
103
+ supplied reusable banks; the runtime must commit/invalidate only resources marked
104
+ in the current invocation's dirty words in `finally`, including when execution
105
+ throws. Conservative resource lists are eligibility guards, not flush lists: a
106
+ resumed suffix must not republish a prefix write after an external event.
107
+ Memory addressing and store values follow the
108
+ generic host's safe-integer checks; `store8` uses Uint8Array wrapping semantics.
109
+
110
+ The supported subset includes number/bool constants, state reads/writes, scalar
111
+ unary/binary arithmetic, bitwise operations, comparisons, numeric builtins, mutable
112
+ scalar locals, static-memory `load8`/`store8` with checked dynamic numeric offsets,
113
+ scalar `select`/`if` regions with value yields,
114
+ and one positive constant `elapse` per instruction. Numeric suffixes may run after
115
+ the elapse, including a captured load's delayed register write and optional `stop`.
116
+ Alternatively a terminal redirect accepts a dynamic finite numeric target with a
117
+ positive constant delay; the target PC is written before time advances. A terminal
118
+ standalone stop is supported. Other control flow, resource parameters, non-state
119
+ reads, dynamic durations, multiple delays, calls, and other host operations fall
120
+ back as complete instructions, never after partially running an unsupported body.
121
+
122
+ Before a body starts, PC is set to `nextPc`. A horizon can split any elapse without
123
+ replaying prefix effects or rereading captured loads. At `time === deadline`, no
124
+ instruction starts and no continuation or post-elapse suffix executes. Absolute
125
+ due times beyond `7200000000000000` throw after preserving completed prefix effects.
126
+ For code invalidation or eligibility changes, retain the old program and frame,
127
+ set `stopAfterInstruction`, and run only that pending instruction to completion.
128
+ The result is `invalidated` before another instruction begins; an idle frame with
129
+ this flag exits immediately. A pending stop instead returns `stopped`.
130
+
131
+ `readStates`, `writeStates`, `readMemories`, and `writeMemories` conservatively cover
132
+ the whole accepted program, including both branches and implicit PC reads/writes.
133
+ The binding runtime must guard these lists against observers, derived/hook read
134
+ dependencies, code memories, waiters, and incompatible ownership before entering.
135
+ The compiler does not choose throughput policy or schedule peripherals: callers
136
+ supply exact horizons, including events and any CPU visibility quantum. Keep the
137
+ generator backend for precise execution and unsupported instructions.
138
+
139
+ Focused tests: build this package, then run
140
+ `node --test packages/compiler/tests/direct-jit.test.mjs` from the repository root.
141
+
142
+ ## First-Release Analysis And Maps
143
+
144
+ Every compilation indexes the original body before optimization. `compileBody` and
145
+ `compileComputation` run the same correctness-preserving constant/use analysis, but
146
+ emit executable functions directly without generating CFG edges, liveness sets,
147
+ recovery maps, source positions or artifact objects. They retain no diagnostic IR.
148
+
149
+ Artifact compilation captures lightweight source/emission information. Its CFG/SSA,
150
+ effects and recovery metadata are lazy, memoized getters; requesting those fields
151
+ lowers regions into an independent, immutable internal graph (`artifact.mir`).
152
+ Merely reading `artifact.execute` or `artifact.source` does not build that graph.
153
+ Operation-granular basic blocks have explicit predecessors, successors, branch/match edges,
154
+ loop backedges, break/continue targets, value-region yields, call completion,
155
+ suspension/resumption and return/throw/stop/redirect exits. Internal instructions
156
+ carry operand IDs, detached attributes, nested-region IDs, original input paths,
157
+ source links and per-operation effects. This is separate from public `RegionIR`;
158
+ the JavaScript emitter keeps native structured control flow, not a CFG interpreter.
159
+
160
+ Results have single-assignment definitions and use lists; select/short-circuit
161
+ joins record phi inputs. Mutable locals remain explicitly identified slots, not
162
+ SSA-renamed local versions. Fixed-point liveness handles loops and is computed both
163
+ for the original baseline and for optimized uses/definitions.
164
+
165
+ The default optimizer propagates known primitive/enum constants through arithmetic,
166
+ comparisons, numeric builtins, optional lifting and none checks. Folded numbers stay
167
+ binary64 values captured outside source text, including NaN and signed zero. An
168
+ unused-result pass removes only total, effect-free scalar computations. Unknown
169
+ intermediates used by original operations are conservatively retained, even if their
170
+ only consumer is dead, so baseline recovery never requires unavailable unknown
171
+ values. Known discarded values have constant materialization recipes. Dynamic bytes,
172
+ reads, calls, assertions, resistor construction, indexing and other potentially
173
+ failing operations remain. A non-null check is removed only for a known non-null
174
+ constant. There is no branch speculation, host-read reuse, effect reordering or
175
+ trust in a call's `pure` annotation. `{ optimize: false }` disables these transforms
176
+ while retaining the same stable baseline position IDs and analysis/maps.
177
+
178
+ Artifacts additionally expose:
179
+
180
+ - `sourceMap`: stable original operation IDs, paths, inherited source locations and
181
+ real generated UTF-16 line/column ranges. Eliminated operations remain present
182
+ with `generated: null`; folded operations retain their original identity.
183
+ - `frameLayout`: typed result/local/context slots and compiler-created condition or
184
+ assertion-message temporaries, linked to actual JavaScript binding names/scopes.
185
+ Constant materialization recipes preserve baseline values removed by folding/DCE.
186
+ Storage is explicitly `native-generator`, not a serializable external frame.
187
+ - `resumeMap`: actual generated yield/yield* lines, continuation locations and live
188
+ slots. Receive results are defined on continuation; delegated results are defined
189
+ on call completion, not before suspension. Terminal signals have no live suffix.
190
+ Delegated resumption retains the native call stack; a position alone cannot
191
+ reconstruct it or replay the call safely.
192
+ - `exitMap` and the exported `ExitState` data contract: source-linked baseline
193
+ positions for return, suspension, terminal control, failure and future optimized
194
+ exits, with required baseline slots versus slots that need capturing. Resume
195
+ positions are after the completed wait (or inside a retained delegate), avoiding
196
+ replay of prefix effects. Guard-exit entries are mapping data, not emitted guards.
197
+ - `dependencies`: resource/callee dependencies plus optional binding identities.
198
+ Calls and wait predicates retain unresolved dependency summaries until resolved
199
+ against the model. `binding` is null unless the caller explicitly supplies
200
+ `{ binding: { instance, entry, generation, codeVersion, instruction } }`; identities
201
+ and code versions are never invented or inferred from resource names.
202
+
203
+ The maps describe real emitted baseline locations and recovery requirements.
204
+ Current execution does **not** capture `ExitState` objects, expose native generator
205
+ locals, serialize frames or implement a speculative/deoptimization executor. A
206
+ consumer must retain the native continuation or provide future capture machinery.
207
+ Internal graph/layout schemas are compiler implementation metadata, not a replacement
208
+ for the stable public model schema.
209
+
210
+ Deferred, rather than claimed complete: local-to-SSA promotion and dominator/range
211
+ analysis; interprocedural call/derived analysis and short-function inlining; alias-
212
+ aware resource read/check CSE; bound numeric resource-slot specialization; optimized
213
+ stack capture and deoptimization execution. The body-only ABI supplies no callee
214
+ bodies or instance binding graph, so this release records conservative dependencies
215
+ instead of making unsupported purity/aliasing assumptions. Model-level validation
216
+ still owns call/derived cycle and effect legality checks.
217
+
218
+ ## Host Evaluation Order
219
+
220
+ Assertions check their saved boolean condition first. A true condition skips both
221
+ message computation and the host assertion call. On failure, the entire lazy message
222
+ region runs before `host.assert(false, message)`; an omitted message is `undefined`.
223
+ Message effects are therefore visible to the host assertion, while a throwing or
224
+ redirecting message never reaches that call. The host must throw for a failed assertion.
225
+
226
+ Memory operations call `host.load`, `host.store`, `host.copyBytes` and
227
+ `host.memoryLength` at their original execution positions. Reads are not hoisted,
228
+ cached or merged across writes or suspension. Generated calls pass the original
229
+ operand values; the host validates resource ownership, nonnegative safe-integer
230
+ byte offsets, complete ranges and safe-integer store values before mutation.
231
+ Multi-byte accesses may be unaligned; store values use width-specific low bits only
232
+ after validation. Byte copies must return independent immutable snapshots.
233
+
234
+ Resource names appear as captured `C[index]` entries in generated source, while
235
+ resource parameters appear as `args[index]`. Inspect `artifact.constants` alongside
236
+ `artifact.source` to resolve captured names. Operation/source positions locate the
237
+ load/store call site, not the inside of the host memory helper. The benchmark's
238
+ DataView host is a small checked implementation of only the two exercised opcodes,
239
+ not a replacement for the simulator's memory and lifecycle implementation.
240
+
241
+ ## Benchmark
242
+
243
+ Build once, then run the bounded Node benchmark from the repository root:
244
+
245
+ ```powershell
246
+ pnpm --dir packages\compiler build
247
+ pnpm --dir packages\compiler bench --smoke
248
+ pnpm --dir packages\compiler bench
249
+ ```
250
+
251
+ The benchmark imports the public compiler package API and constructs new-model IR.
252
+ It measures arithmetic and arithmetic-plus-memory kernels with native generated
253
+ loops, parameterized seeds and loop counts. The memory kernel stores little-endian
254
+ 32-bit values through a resource parameter and reads big-endian values through a
255
+ direct resource name at unaligned byte offsets.
256
+
257
+ Each row reports first uncached `compileBody` time, subsequent artifact construction
258
+ time, first lazy metadata materialization time, generated source length in UTF-16
259
+ code units, median warmed batch time, executions/second, rounds/second and memory
260
+ accesses/second. Baseline compilation includes optimization, emission and
261
+ `new Function`, but excludes diagnostic CFG/liveness/recovery work, module import,
262
+ fixture creation, package build and first execution. Artifact construction is timed
263
+ after the baseline, so it can benefit from JavaScript engine source caching; it is
264
+ not presented as a second cold measurement. Metadata timing explicitly forces all
265
+ analysis/layout maps, verifies memoization, and is outside execution timing.
266
+ Cold means the first uncached baseline compilation of each kernel in this process,
267
+ not fresh-process startup; arithmetic is measured before memory.
268
+ Warmed timings use the fast baseline executable and include generator creation,
269
+ one `.next()`, host range/value checks and checksum accumulation. JavaScript reference calculations and preflight checks
270
+ are outside measured batches; results are consumed and verified against reference
271
+ checksums. These are local microbenchmarks, not simulator throughput or latency.
272
+
273
+ Smoke mode uses 100 warmups, three 1,000-execution samples and 64 rounds per
274
+ execution. Default mode uses 2,000 warmups, five 10,000-execution samples and 128
275
+ rounds. Both modes do fixed, bounded work and add no unit tests. Compare results
276
+ using the same Node version and machine; short smoke timings are especially noisy.