@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 +21 -0
- package/README.md +275 -2
- package/dist/index.d.ts +402 -0
- package/dist/index.js +1886 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -3
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
|
-
#
|
|
1
|
+
# @mica-js/compiler
|
|
2
2
|
|
|
3
|
-
|
|
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.
|