numbl 0.4.18 → 0.4.19

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.
Files changed (31) hide show
  1. package/dist-browser/browser.js +1 -1
  2. package/dist-browser/numbl-core/executors/jit/callExecutor.d.ts +7 -1
  3. package/dist-browser/numbl-core/executors/jit/loopExecutor.d.ts +12 -7
  4. package/dist-browser/numbl-core/executors/jit/topLevelExecutor.d.ts +3 -0
  5. package/dist-browser/numbl-core/executors/jit/valueAdapter.d.ts +9 -2
  6. package/dist-browser/numbl-core/executors/registry.d.ts +8 -0
  7. package/dist-browser/numbl-core/interpreter/interpreter.d.ts +11 -0
  8. package/dist-browser/numbl-core/jit/builtins/runtime/system/cscalar.d.ts +4 -4
  9. package/dist-browser/numbl-core/jit/codegen/emitIndex.d.ts +1 -1
  10. package/dist-browser/numbl-core/jit/compileSpec.d.ts +4 -0
  11. package/dist-browser/numbl-core/jit/lowering/lower.d.ts +13 -0
  12. package/dist-browser/numbl-core/jitDeclineDiagnostics.d.ts +6 -0
  13. package/dist-browser/numbl-core/version.d.ts +1 -1
  14. package/dist-cli/cli.js +361 -109
  15. package/dist-lib/lib.js +298 -67
  16. package/dist-lib/numbl-core/executors/jit/callExecutor.d.ts +7 -1
  17. package/dist-lib/numbl-core/executors/jit/loopExecutor.d.ts +12 -7
  18. package/dist-lib/numbl-core/executors/jit/topLevelExecutor.d.ts +3 -0
  19. package/dist-lib/numbl-core/executors/jit/valueAdapter.d.ts +9 -2
  20. package/dist-lib/numbl-core/executors/registry.d.ts +8 -0
  21. package/dist-lib/numbl-core/interpreter/interpreter.d.ts +11 -0
  22. package/dist-lib/numbl-core/jit/builtins/runtime/system/cscalar.d.ts +4 -4
  23. package/dist-lib/numbl-core/jit/codegen/emitIndex.d.ts +1 -1
  24. package/dist-lib/numbl-core/jit/compileSpec.d.ts +4 -0
  25. package/dist-lib/numbl-core/jit/lowering/lower.d.ts +13 -0
  26. package/dist-lib/numbl-core/jitDeclineDiagnostics.d.ts +6 -0
  27. package/dist-lib/numbl-core/version.d.ts +1 -1
  28. package/dist-site-viewer/assets/{index-34LVPucU.js → index-B5Y5557F.js} +252 -252
  29. package/dist-site-viewer/assets/{numbl-worker-BCMSGYEy.js → numbl-worker-CtoT7C0f.js} +103 -87
  30. package/dist-site-viewer/index.html +1 -1
  31. package/package.json +1 -1
package/dist-lib/lib.js CHANGED
@@ -21826,7 +21826,7 @@ function applyUnaryElemwiseMaybeComplex(v, realFn, complexFn, name, nativeOpCode
21826
21826
  if (typeof v === "boolean") v = v ? 1 : 0;
21827
21827
  if (typeof v === "number") {
21828
21828
  const r = realFn(v);
21829
- if (!Number.isNaN(r)) return r;
21829
+ if (!Number.isNaN(r) || Number.isNaN(v)) return r;
21830
21830
  const c = complexFn(v, 0);
21831
21831
  return mkc(c.re, c.im);
21832
21832
  }
@@ -21842,7 +21842,7 @@ function applyUnaryElemwiseMaybeComplex(v, realFn, complexFn, name, nativeOpCode
21842
21842
  tensorOps.realUnaryElemwise(nativeOpCode, n, v.data, nativeOut);
21843
21843
  let firstNaN = -1;
21844
21844
  for (let i = 0; i < n; i++) {
21845
- if (Number.isNaN(nativeOut[i])) {
21845
+ if (Number.isNaN(nativeOut[i]) && !Number.isNaN(v.data[i])) {
21846
21846
  firstNaN = i;
21847
21847
  break;
21848
21848
  }
@@ -21854,7 +21854,7 @@ function applyUnaryElemwiseMaybeComplex(v, realFn, complexFn, name, nativeOpCode
21854
21854
  const outI3 = allocFloat64Array(n);
21855
21855
  let hasImag2 = false;
21856
21856
  for (let i = 0; i < n; i++) {
21857
- if (!Number.isNaN(nativeOut[i])) {
21857
+ if (!Number.isNaN(nativeOut[i]) || Number.isNaN(v.data[i])) {
21858
21858
  outR3[i] = nativeOut[i];
21859
21859
  } else {
21860
21860
  const c = complexFn(v.data[i], 0);
@@ -21870,7 +21870,7 @@ function applyUnaryElemwiseMaybeComplex(v, realFn, complexFn, name, nativeOpCode
21870
21870
  let hasImag = false;
21871
21871
  for (let i = 0; i < n; i++) {
21872
21872
  const r = realFn(v.data[i]);
21873
- if (!Number.isNaN(r)) {
21873
+ if (!Number.isNaN(r) || Number.isNaN(v.data[i])) {
21874
21874
  outR2[i] = r;
21875
21875
  outI2[i] = 0;
21876
21876
  } else {
@@ -48811,8 +48811,9 @@ defineBuiltin({
48811
48811
  cases: [
48812
48812
  {
48813
48813
  match: (argTypes) => argTypes.length === 0 ? [{ kind: "number" }] : null,
48814
- apply: () => {
48814
+ apply: (_args, nargout) => {
48815
48815
  ticTime = performance.now();
48816
+ if (nargout === 0) return void 0;
48816
48817
  return RTV.num(ticTime / 1e3);
48817
48818
  }
48818
48819
  }
@@ -56866,6 +56867,7 @@ function registerSpecialBuiltins(rt) {
56866
56867
  if (na === 0) {
56867
56868
  rt.output(`Elapsed time is ${elapsed.toFixed(6)} seconds.
56868
56869
  `);
56870
+ return void 0;
56869
56871
  }
56870
56872
  return RTV.num(elapsed);
56871
56873
  }
@@ -65578,8 +65580,7 @@ var Registry = class {
65578
65580
  const stmt = siblings[i];
65579
65581
  const lowered = tryLower(siblings, i, ctx.interp, this.loweringCache);
65580
65582
  if (lowered === null) {
65581
- const signal2 = ctx.interp.execStmt(stmt);
65582
- return { signal: signal2 };
65583
+ return this.interpretFallback(ctx, stmt);
65583
65584
  }
65584
65585
  let bestExec = null;
65585
65586
  let bestData = null;
@@ -65628,8 +65629,25 @@ var Registry = class {
65628
65629
  }
65629
65630
  }
65630
65631
  }
65631
- const signal = ctx.interp.execStmt(stmt);
65632
- return { signal };
65632
+ return this.interpretFallback(ctx, stmt);
65633
+ }
65634
+ /** Run one stmt on the AST interpreter.
65635
+ *
65636
+ * When that stmt is a loop, the interpreter is about to walk its body,
65637
+ * which invalidates the "an enclosing loop will capture me" assumption
65638
+ * behind the executors' `loopDepth` gate. `jitDeclinedLoopDepth` records
65639
+ * that for the duration of the body so nested loops and calls can bid for
65640
+ * themselves instead of running interpreted all the way down. */
65641
+ interpretFallback(ctx, stmt) {
65642
+ if (stmt.type === "For" || stmt.type === "While") {
65643
+ ctx.interp.jitDeclinedLoopDepth++;
65644
+ try {
65645
+ return { signal: ctx.interp.execStmt(stmt) };
65646
+ } finally {
65647
+ ctx.interp.jitDeclinedLoopDepth--;
65648
+ }
65649
+ }
65650
+ return { signal: ctx.interp.execStmt(stmt) };
65633
65651
  }
65634
65652
  runStmtCandidate(stmt, ctx, executor, data, lowered) {
65635
65653
  const result = this.executeCandidate(ctx, executor, stmt, data, stmt);
@@ -65865,6 +65883,10 @@ __export(interpreterExec_exports, {
65865
65883
  var lastDecline = null;
65866
65884
  function recordJitDecline(d) {
65867
65885
  lastDecline = d;
65886
+ if (typeof process !== "undefined" && process.env?.NUMBL_JIT_LOG) {
65887
+ const at = d.at ? ` [${d.at.file}@${d.at.start}]` : "";
65888
+ console.error(`[jit-decline] ${d.where}${at}: ${d.message}`);
65889
+ }
65868
65890
  }
65869
65891
  function getLastJitDecline() {
65870
65892
  return lastDecline;
@@ -67853,6 +67875,7 @@ function interpretWorkspaceFunction(target, args, nargout) {
67853
67875
  fn = funcDefFromStmt(stmt);
67854
67876
  break;
67855
67877
  }
67878
+ if (stmt.type !== "ClassDef" && stmt.type !== "Import") break;
67856
67879
  }
67857
67880
  }
67858
67881
  }
@@ -68652,6 +68675,17 @@ var Interpreter = class {
68652
68675
  * interpreter never sees them, so this gate only fires on calls
68653
68676
  * that genuinely happen at the interpreter level inside a loop. */
68654
68677
  loopDepth = 0;
68678
+ /** @internal Number of those enclosing loops that the JIT *declined*, so
68679
+ * the interpreter is walking their bodies statement by statement. The
68680
+ * `loopDepth > 0` gate above assumes an enclosing loop will be compiled
68681
+ * and will swallow whatever is nested inside it; when that loop was
68682
+ * declined instead, the assumption is false and everything below it would
68683
+ * run interpreted however expensive it is. The executors therefore gate on
68684
+ * `loopDepth > 0 && jitDeclinedLoopDepth === 0`: still no per-iteration JIT
68685
+ * attempts underneath a loop that is about to be compiled, but a nested
68686
+ * loop or call under a declined one gets its own chance. Bumped by the
68687
+ * registry around the interpreter fallback for a loop stmt. */
68688
+ jitDeclinedLoopDepth = 0;
68655
68689
  /** @internal Number of enclosing conditional blocks (`if` / `switch` /
68656
68690
  * `try`) whose bodies the interpreter is currently executing. Used by
68657
68691
  * the loop classifier: when a loop is dispatched with `condBlockDepth >
@@ -71837,8 +71871,12 @@ function mtoc2_cpow(a, b) {
71837
71871
  }
71838
71872
  return cexp2(mtoc2_cmul(b, clog2(a)));
71839
71873
  }
71874
+ function nanOnRealAxis(z) {
71875
+ return z.im === 0 && Number.isNaN(z.re);
71876
+ }
71840
71877
  function mtoc2_csqrt(z) {
71841
71878
  if (z.im === 0) {
71879
+ if (Number.isNaN(z.re)) return { re: z.re, im: 0 };
71842
71880
  if (z.re >= 0) return { re: Math.sqrt(z.re), im: 0 };
71843
71881
  return { re: 0, im: Math.sqrt(-z.re) };
71844
71882
  }
@@ -71852,14 +71890,17 @@ function mtoc2_cexp(z) {
71852
71890
  return cexp2(z);
71853
71891
  }
71854
71892
  function mtoc2_clog(z) {
71893
+ if (nanOnRealAxis(z)) return { re: z.re, im: 0 };
71855
71894
  return clog2(z);
71856
71895
  }
71857
71896
  function mtoc2_clog2(z) {
71897
+ if (nanOnRealAxis(z)) return { re: z.re, im: 0 };
71858
71898
  const l = clog2(z);
71859
71899
  const lg2 = Math.log(2);
71860
71900
  return { re: l.re / lg2, im: l.im / lg2 };
71861
71901
  }
71862
71902
  function mtoc2_clog10(z) {
71903
+ if (nanOnRealAxis(z)) return { re: z.re, im: 0 };
71863
71904
  const l = clog2(z);
71864
71905
  const lg10 = Math.log(10);
71865
71906
  return { re: l.re / lg10, im: l.im / lg10 };
@@ -74270,7 +74311,7 @@ var JS_SNIPPETS = {
74270
74311
  "warning_fmt.js": '// JS sibling of `warning_fmt.h`. Format the message and write\n// `Warning: <msg>\\n` via `$write`. Matches numbl\n// (`runtime/specialBuiltins.ts:190` \u2014 `rt.output("Warning: " + msg + "\\n")`).\n\n\nfunction mtoc2_warning_fmt(fmt, ...args) {\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n\nfunction mtoc2_warning_fmt_id(_id, fmt, ...args) {\n // The id is informational \u2014 numbl threads it to lastwarn state,\n // which mtoc2 doesn\'t expose. Drop it here so the visible output\n // matches the bare form.\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n',
74271
74312
  "plot_dispatch.js": '// JS sibling of `plot_dispatch.h`. Two delivery modes:\n//\n// 1. Host hook mode (numbl JIT bridge): when the host binds a\n// `$plotDispatch(name, args)` callback into the emit\'s `$h`\n// helpers, the helper forwards the raw mtoc2-shaped args to the\n// host. The host is expected to translate the args into its own\n// runtime value shape and route them into its plot pipeline\n// (numbl: `dispatchPlotBuiltin` into `rt.plotInstructions`).\n//\n// Wire-format encoding is skipped entirely in this mode \u2014 the\n// host operates in-process and consumes the same value graph the\n// emitted code already holds.\n//\n// 2. Wire-format mode (standalone AOT / viewer tee): when no host\n// hook is present, fall back to the RS-prefixed JSON record on\n// stdout that the launcher splits and forwards. Same shape the C\n// sibling emits, so a single viewer parses both backends.\n//\n// Wire shape per call (mode 2):\n// \\x1e mtoc2:plot \\t {"call":"<name>","args":[<arg>, ...]} \\n\n//\n// Arg encoding (per source-level arg):\n// number \u2192 bare numeric (non-finite \u2192 null, matching the C side\n// and JSON.stringify\'s natural behavior)\n// string \u2192 {"kind":"text","data":"<text>"}\n// char \u2192 same as string (MATLAB plot semantics don\'t distinguish)\n// tensor \u2192 {"kind":"tensor","dims":[\u2026],"data":[\u2026]} (column-major\n// flatten; non-finite as null)\n\nfunction isTensor(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "tensor";\n}\nfunction isChar(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "char";\n}\n\nfunction encodeArg(v) {\n if (typeof v === "number") {\n return Number.isFinite(v) ? v : null;\n }\n if (typeof v === "boolean") return v ? 1 : 0;\n if (typeof v === "string") return { kind: "text", data: v };\n if (isChar(v)) return { kind: "text", data: v.value };\n if (isTensor(v)) {\n const data = new Array(v.data.length);\n for (let i = 0; i < v.data.length; i++) {\n const x = v.data[i];\n data[i] = Number.isFinite(x) ? x : null;\n }\n return { kind: "tensor", dims: v.shape.slice(), data };\n }\n // Unknown shape \u2014 pass through. Type-level rejection on the\n // lowering side keeps complex/struct/class/handle out of here.\n return String(v);\n}\n\nfunction mtoc2_plot_dispatch(name, ...args) {\n if (typeof globalThis.$plotDispatch === "function") {\n globalThis.$plotDispatch(name, args);\n return;\n }\n const record = { call: name, args: args.map(encodeArg) };\n $write("\\x1emtoc2:plot\\t" + JSON.stringify(record) + "\\n");\n}\n',
74272
74313
  "cdiv.js": "// JS sibling of `cdiv.h`. Scalar complex division matching numbl's\n// signed-Inf-on-zero-divisor behavior via Smith's algorithm. Mirrors\n// the C side so cross-runner output stays byte-for-byte.\n\nfunction mtoc2_cdiv(a, b) {\n const ar = a.re,\n ai = a.im;\n const br = b.re,\n bi = b.im;\n // Zero divisor: match the interpreter (helpers/arithmetic.ts) \u2014 0/0 is\n // NaN, a nonzero numerator yields a signed Inf per component. Smith's\n // algorithm below would otherwise produce NaN+NaNi here.\n if (br === 0 && bi === 0) {\n if (ar === 0 && ai === 0) return { re: NaN, im: 0 };\n const signedInf = x => (x > 0 ? Infinity : x < 0 ? -Infinity : 0);\n return { re: signedInf(ar), im: signedInf(ai) };\n }\n if (Math.abs(br) >= Math.abs(bi)) {\n const r = bi / br;\n const den = br + r * bi;\n return { re: (ar + ai * r) / den, im: (ai - ar * r) / den };\n }\n const r = br / bi;\n const den = bi + r * br;\n return { re: (ar * r + ai) / den, im: (ai * r - ar) / den };\n}\n",
74273
- "cscalar.js": "// JS sibling of `cscalar.h`. JS has no native complex type, so each\n// helper takes and returns `{re, im}` objects. Mirrors the C\n// signatures so emitJs renders calls structurally identical to the\n// C path (just with JS objects instead of `double _Complex`).\n//\n// Division (`mtoc2_cdiv`) lives in `cdiv.js` because it needs\n// Smith's algorithm with signed-zero detection to match numbl\n// byte-for-byte.\n\nfunction mtoc2_cmake(re, im) {\n return { re, im };\n}\nfunction mtoc2_creal(z) {\n return z.re;\n}\nfunction mtoc2_cimag(z) {\n return z.im;\n}\nfunction mtoc2_cadd(a, b) {\n return { re: a.re + b.re, im: a.im + b.im };\n}\nfunction mtoc2_csub(a, b) {\n return { re: a.re - b.re, im: a.im - b.im };\n}\nfunction mtoc2_cmul(a, b) {\n return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re };\n}\nfunction mtoc2_cneg(z) {\n return { re: -z.re, im: -z.im };\n}\nfunction mtoc2_cconj(z) {\n return { re: z.re, im: -z.im };\n}\nfunction mtoc2_cabs(z) {\n return Math.hypot(z.re, z.im);\n}\nfunction mtoc2_cangle(z) {\n return Math.atan2(z.im, z.re);\n}\nfunction mtoc2_cnonzero(z) {\n return z.re !== 0 || z.im !== 0;\n}\nfunction mtoc2_ceq(a, b) {\n return a.re === b.re && a.im === b.im;\n}\nfunction mtoc2_cne(a, b) {\n return a.re !== b.re || a.im !== b.im;\n}\n\n// `cpow(a, b) = exp(b * log(a))`. Matches numbl / C99 `cpow` for\n// finite inputs; special zero / pole cases follow the same path\n// the C side picks (exp(NaN) = NaN, etc.).\nfunction clog(z) {\n // Normalize a -0 real part to +0 so log(-0) (a real -0) gives\n // -Inf + 0i, not -Inf + \u03C0i \u2014 MATLAB / C99 treat -0 as 0, not as an\n // approach-from-below negative. atan2's -0 second arg only changes the\n // result when the first arg is also 0, so this affects nothing else.\n const reAngle = z.re === 0 ? 0 : z.re;\n return {\n re: Math.log(Math.hypot(z.re, z.im)),\n im: Math.atan2(z.im, reAngle),\n };\n}\nfunction cexp(z) {\n const m = Math.exp(z.re);\n // Real-valued input (im === 0): the imaginary lane is m*sin(0) = 0, but\n // when m overflows to Inf that becomes Inf*0 = NaN. Return a clean 0\n // (matches the interpreter's real exp and C99 cexp).\n if (z.im === 0) return { re: m, im: 0 };\n return { re: m * Math.cos(z.im), im: m * Math.sin(z.im) };\n}\nfunction mtoc2_cpow(a, b) {\n // Zero base: match the interpreter's complexPow (helpers/arithmetic.ts)\n // \u2014 0^0 = 1, a positive real part of the exponent gives 0 (regardless\n // of the imaginary part), and anything else gives Inf+0i. The old\n // guard required b.im===0 for the zero case and otherwise returned\n // NaN+NaNi.\n if (a.re === 0 && a.im === 0) {\n if (b.re === 0 && b.im === 0) return { re: 1, im: 0 };\n if (b.re > 0) return { re: 0, im: 0 };\n return { re: Infinity, im: 0 };\n }\n return cexp(mtoc2_cmul(b, clog(a)));\n}\n\n// Unary math.\nfunction mtoc2_csqrt(z) {\n // Pure-real input: compute directly. Smith's formula below suffers\n // catastrophic cancellation/overflow when z.im === 0 \u2014 e.g.\n // sqrt(-Inf) \u2192 sqrt((Inf + -Inf)/2) = sqrt(NaN) = NaN, and\n // sqrt(-1e308) \u2192 sqrt((1e308 - -1e308)/2) overflows to Inf instead of\n // 1e154. Mirror the interpreter's complexSqrt (math.ts) and C's libm\n // csqrt, both of which special-case the real axis.\n if (z.im === 0) {\n if (z.re >= 0) return { re: Math.sqrt(z.re), im: 0 };\n return { re: 0, im: Math.sqrt(-z.re) };\n }\n // Smith's stable formula for hypot * sign.\n const r = Math.hypot(z.re, z.im);\n if (r === 0) return { re: 0, im: 0 };\n const re = Math.sqrt((r + z.re) / 2);\n const im = (z.im >= 0 ? 1 : -1) * Math.sqrt((r - z.re) / 2);\n return { re, im };\n}\nfunction mtoc2_cexp(z) {\n return cexp(z);\n}\nfunction mtoc2_clog(z) {\n return clog(z);\n}\nfunction mtoc2_clog2(z) {\n const l = clog(z);\n const lg2 = Math.log(2);\n return { re: l.re / lg2, im: l.im / lg2 };\n}\nfunction mtoc2_clog10(z) {\n const l = clog(z);\n const lg10 = Math.log(10);\n return { re: l.re / lg10, im: l.im / lg10 };\n}\nfunction mtoc2_csin(z) {\n // Guard 0*Inf=NaN on the imaginary axis: for a pure-imaginary input\n // (sin(z.re)===0), the real lane is 0 and the imaginary lane is\n // \xB1sinh(im); cosh/sinh overflow to Inf for large |im|, and 0*Inf would\n // poison the result. C99 csin special-cases this \u2014 match it.\n const sr = Math.sin(z.re);\n const cr = Math.cos(z.re);\n return {\n re: sr === 0 ? 0 : sr * Math.cosh(z.im),\n im: cr === 0 ? 0 : cr * Math.sinh(z.im),\n };\n}\nfunction mtoc2_ccos(z) {\n const cr = Math.cos(z.re);\n const sr = Math.sin(z.re);\n return {\n re: cr === 0 ? 0 : cr * Math.cosh(z.im),\n im: sr === 0 ? 0 : -sr * Math.sinh(z.im),\n };\n}\nfunction mtoc2_ctan(z) {\n // Double-angle form, matching the interpreter (interpreter/builtins/\n // math.ts) and C99 ctan. Computing sin(z)/cos(z) with a |cos|^2\n // denominator loses the (tiny but nonzero) real part and overflows\n // for large imaginary parts \u2014 e.g. tan(1+200i) collapsed to 1i.\n const denom = Math.cos(2 * z.re) + Math.cosh(2 * z.im);\n return { re: Math.sin(2 * z.re) / denom, im: Math.sinh(2 * z.im) / denom };\n}\nfunction mtoc2_catan(z) {\n // Real-valued input (im === 0): atan is a clean real function. The\n // complex formula leaves a spurious \xB11e-17i residue (and NaN+NaNi once\n // 1\xB1iz overflows, e.g. atan(1e300)). Take the real path.\n if (z.im === 0) return { re: Math.atan(z.re), im: 0 };\n // atan(z) = (i/2)\xB7log((1 \u2212 iz)/(1 + iz)) \u2014 MATLAB's branch (atan(2i) =\n // \u22121.5708 + 0.5493i). The C runtime overrides its libc catan (which\n // uses the opposite +\u03C0/2 Annex-G branch) to match this.\n const iz = { re: -z.im, im: z.re }; // i*z\n const num = { re: 1 - iz.re, im: -iz.im }; // 1 - iz\n const denom = { re: 1 + iz.re, im: iz.im }; // 1 + iz\n const dd = denom.re * denom.re + denom.im * denom.im;\n const q = {\n re: (num.re * denom.re + num.im * denom.im) / dd,\n im: (num.im * denom.re - num.re * denom.im) / dd,\n };\n const l = clog(q);\n return { re: -l.im / 2, im: l.re / 2 };\n}\n// Hyperbolic sinh/cosh/tanh \u2014 byte-for-byte with the interpreter's\n// complex formulas (interpreter/builtins/math.ts). Real-valued input\n// (im === 0) falls out cleanly: cos(0)=1, sin(0)=0, so the imaginary\n// lane is exactly 0.\nfunction mtoc2_csinh(z) {\n return {\n re: Math.sinh(z.re) * Math.cos(z.im),\n im: Math.cosh(z.re) * Math.sin(z.im),\n };\n}\nfunction mtoc2_ccosh(z) {\n return {\n re: Math.cosh(z.re) * Math.cos(z.im),\n im: Math.sinh(z.re) * Math.sin(z.im),\n };\n}\nfunction mtoc2_ctanh(z) {\n const denom = Math.cosh(2 * z.re) + Math.cos(2 * z.im);\n return { re: Math.sinh(2 * z.re) / denom, im: Math.sin(2 * z.im) / denom };\n}\nfunction mtoc2_cfloor(z) {\n return { re: Math.floor(z.re), im: Math.floor(z.im) };\n}\nfunction mtoc2_cceil(z) {\n return { re: Math.ceil(z.re), im: Math.ceil(z.im) };\n}\nfunction mtoc2_cround(z) {\n // MATLAB rounds each component half-away-from-zero.\n const half = x => Math.sign(x) * Math.round(Math.abs(x));\n return { re: half(z.re), im: half(z.im) };\n}\nfunction mtoc2_cfix(z) {\n return { re: Math.trunc(z.re), im: Math.trunc(z.im) };\n}\nfunction mtoc2_csign(z) {\n if (z.re === 0 && z.im === 0) return { re: 0, im: 0 };\n const m = Math.hypot(z.re, z.im);\n return { re: z.re / m, im: z.im / m };\n}\n",
74314
+ "cscalar.js": "// JS sibling of `cscalar.h`. JS has no native complex type, so each\n// helper takes and returns `{re, im}` objects. Mirrors the C\n// signatures so emitJs renders calls structurally identical to the\n// C path (just with JS objects instead of `double _Complex`).\n//\n// Division (`mtoc2_cdiv`) lives in `cdiv.js` because it needs\n// Smith's algorithm with signed-zero detection to match numbl\n// byte-for-byte.\n\nfunction mtoc2_cmake(re, im) {\n return { re, im };\n}\nfunction mtoc2_creal(z) {\n return z.re;\n}\nfunction mtoc2_cimag(z) {\n return z.im;\n}\nfunction mtoc2_cadd(a, b) {\n return { re: a.re + b.re, im: a.im + b.im };\n}\nfunction mtoc2_csub(a, b) {\n return { re: a.re - b.re, im: a.im - b.im };\n}\nfunction mtoc2_cmul(a, b) {\n return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re };\n}\nfunction mtoc2_cneg(z) {\n return { re: -z.re, im: -z.im };\n}\nfunction mtoc2_cconj(z) {\n return { re: z.re, im: -z.im };\n}\nfunction mtoc2_cabs(z) {\n return Math.hypot(z.re, z.im);\n}\nfunction mtoc2_cangle(z) {\n return Math.atan2(z.im, z.re);\n}\nfunction mtoc2_cnonzero(z) {\n return z.re !== 0 || z.im !== 0;\n}\nfunction mtoc2_ceq(a, b) {\n return a.re === b.re && a.im === b.im;\n}\nfunction mtoc2_cne(a, b) {\n return a.re !== b.re || a.im !== b.im;\n}\n\n// `cpow(a, b) = exp(b * log(a))`. Matches numbl / C99 `cpow` for\n// finite inputs; special zero / pole cases follow the same path\n// the C side picks (exp(NaN) = NaN, etc.).\nfunction clog(z) {\n // Normalize a -0 real part to +0 so log(-0) (a real -0) gives\n // -Inf + 0i, not -Inf + \u03C0i \u2014 MATLAB / C99 treat -0 as 0, not as an\n // approach-from-below negative. atan2's -0 second arg only changes the\n // result when the first arg is also 0, so this affects nothing else.\n const reAngle = z.re === 0 ? 0 : z.re;\n return {\n re: Math.log(Math.hypot(z.re, z.im)),\n im: Math.atan2(z.im, reAngle),\n };\n}\nfunction cexp(z) {\n const m = Math.exp(z.re);\n // Real-valued input (im === 0): the imaginary lane is m*sin(0) = 0, but\n // when m overflows to Inf that becomes Inf*0 = NaN. Return a clean 0\n // (matches the interpreter's real exp and C99 cexp).\n if (z.im === 0) return { re: m, im: 0 };\n return { re: m * Math.cos(z.im), im: m * Math.sin(z.im) };\n}\nfunction mtoc2_cpow(a, b) {\n // Zero base: match the interpreter's complexPow (helpers/arithmetic.ts)\n // \u2014 0^0 = 1, a positive real part of the exponent gives 0 (regardless\n // of the imaginary part), and anything else gives Inf+0i. The old\n // guard required b.im===0 for the zero case and otherwise returned\n // NaN+NaNi.\n if (a.re === 0 && a.im === 0) {\n if (b.re === 0 && b.im === 0) return { re: 1, im: 0 };\n if (b.re > 0) return { re: 0, im: 0 };\n return { re: Infinity, im: 0 };\n }\n return cexp(mtoc2_cmul(b, clog(a)));\n}\n\n/** True when `z` is a NaN sitting on the real axis.\n *\n * These helpers are reached both from genuinely complex arithmetic and from\n * the *lift* the JIT performs on a real argument whose sign it cannot prove\n * (`sqrt(x)`, `log(x)`). MATLAB's sqrt(NaN) and log(NaN) are a real NaN, so a\n * lifted NaN must come back as NaN + 0i and not, say, 0 + NaN i: the lift is\n * a compilation detail and must not change what `real`, `isreal`,\n * `jsonencode` or a printed value say. */\nfunction nanOnRealAxis(z) {\n return z.im === 0 && Number.isNaN(z.re);\n}\n\n// Unary math.\nfunction mtoc2_csqrt(z) {\n // Pure-real input: compute directly. Smith's formula below suffers\n // catastrophic cancellation/overflow when z.im === 0 \u2014 e.g.\n // sqrt(-Inf) \u2192 sqrt((Inf + -Inf)/2) = sqrt(NaN) = NaN, and\n // sqrt(-1e308) \u2192 sqrt((1e308 - -1e308)/2) overflows to Inf instead of\n // 1e154. Mirror the interpreter's complexSqrt (math.ts) and C's libm\n // csqrt, both of which special-case the real axis.\n if (z.im === 0) {\n if (Number.isNaN(z.re)) return { re: z.re, im: 0 };\n if (z.re >= 0) return { re: Math.sqrt(z.re), im: 0 };\n return { re: 0, im: Math.sqrt(-z.re) };\n }\n // Smith's stable formula for hypot * sign.\n const r = Math.hypot(z.re, z.im);\n if (r === 0) return { re: 0, im: 0 };\n const re = Math.sqrt((r + z.re) / 2);\n const im = (z.im >= 0 ? 1 : -1) * Math.sqrt((r - z.re) / 2);\n return { re, im };\n}\nfunction mtoc2_cexp(z) {\n return cexp(z);\n}\nfunction mtoc2_clog(z) {\n if (nanOnRealAxis(z)) return { re: z.re, im: 0 };\n return clog(z);\n}\nfunction mtoc2_clog2(z) {\n if (nanOnRealAxis(z)) return { re: z.re, im: 0 };\n const l = clog(z);\n const lg2 = Math.log(2);\n return { re: l.re / lg2, im: l.im / lg2 };\n}\nfunction mtoc2_clog10(z) {\n if (nanOnRealAxis(z)) return { re: z.re, im: 0 };\n const l = clog(z);\n const lg10 = Math.log(10);\n return { re: l.re / lg10, im: l.im / lg10 };\n}\nfunction mtoc2_csin(z) {\n // Guard 0*Inf=NaN on the imaginary axis: for a pure-imaginary input\n // (sin(z.re)===0), the real lane is 0 and the imaginary lane is\n // \xB1sinh(im); cosh/sinh overflow to Inf for large |im|, and 0*Inf would\n // poison the result. C99 csin special-cases this \u2014 match it.\n const sr = Math.sin(z.re);\n const cr = Math.cos(z.re);\n return {\n re: sr === 0 ? 0 : sr * Math.cosh(z.im),\n im: cr === 0 ? 0 : cr * Math.sinh(z.im),\n };\n}\nfunction mtoc2_ccos(z) {\n const cr = Math.cos(z.re);\n const sr = Math.sin(z.re);\n return {\n re: cr === 0 ? 0 : cr * Math.cosh(z.im),\n im: sr === 0 ? 0 : -sr * Math.sinh(z.im),\n };\n}\nfunction mtoc2_ctan(z) {\n // Double-angle form, matching the interpreter (interpreter/builtins/\n // math.ts) and C99 ctan. Computing sin(z)/cos(z) with a |cos|^2\n // denominator loses the (tiny but nonzero) real part and overflows\n // for large imaginary parts \u2014 e.g. tan(1+200i) collapsed to 1i.\n const denom = Math.cos(2 * z.re) + Math.cosh(2 * z.im);\n return { re: Math.sin(2 * z.re) / denom, im: Math.sinh(2 * z.im) / denom };\n}\nfunction mtoc2_catan(z) {\n // Real-valued input (im === 0): atan is a clean real function. The\n // complex formula leaves a spurious \xB11e-17i residue (and NaN+NaNi once\n // 1\xB1iz overflows, e.g. atan(1e300)). Take the real path.\n if (z.im === 0) return { re: Math.atan(z.re), im: 0 };\n // atan(z) = (i/2)\xB7log((1 \u2212 iz)/(1 + iz)) \u2014 MATLAB's branch (atan(2i) =\n // \u22121.5708 + 0.5493i). The C runtime overrides its libc catan (which\n // uses the opposite +\u03C0/2 Annex-G branch) to match this.\n const iz = { re: -z.im, im: z.re }; // i*z\n const num = { re: 1 - iz.re, im: -iz.im }; // 1 - iz\n const denom = { re: 1 + iz.re, im: iz.im }; // 1 + iz\n const dd = denom.re * denom.re + denom.im * denom.im;\n const q = {\n re: (num.re * denom.re + num.im * denom.im) / dd,\n im: (num.im * denom.re - num.re * denom.im) / dd,\n };\n const l = clog(q);\n return { re: -l.im / 2, im: l.re / 2 };\n}\n// Hyperbolic sinh/cosh/tanh \u2014 byte-for-byte with the interpreter's\n// complex formulas (interpreter/builtins/math.ts). Real-valued input\n// (im === 0) falls out cleanly: cos(0)=1, sin(0)=0, so the imaginary\n// lane is exactly 0.\nfunction mtoc2_csinh(z) {\n return {\n re: Math.sinh(z.re) * Math.cos(z.im),\n im: Math.cosh(z.re) * Math.sin(z.im),\n };\n}\nfunction mtoc2_ccosh(z) {\n return {\n re: Math.cosh(z.re) * Math.cos(z.im),\n im: Math.sinh(z.re) * Math.sin(z.im),\n };\n}\nfunction mtoc2_ctanh(z) {\n const denom = Math.cosh(2 * z.re) + Math.cos(2 * z.im);\n return { re: Math.sinh(2 * z.re) / denom, im: Math.sin(2 * z.im) / denom };\n}\nfunction mtoc2_cfloor(z) {\n return { re: Math.floor(z.re), im: Math.floor(z.im) };\n}\nfunction mtoc2_cceil(z) {\n return { re: Math.ceil(z.re), im: Math.ceil(z.im) };\n}\nfunction mtoc2_cround(z) {\n // MATLAB rounds each component half-away-from-zero.\n const half = x => Math.sign(x) * Math.round(Math.abs(x));\n return { re: half(z.re), im: half(z.im) };\n}\nfunction mtoc2_cfix(z) {\n return { re: Math.trunc(z.re), im: Math.trunc(z.im) };\n}\nfunction mtoc2_csign(z) {\n if (z.re === 0 && z.im === 0) return { re: 0, im: 0 };\n const m = Math.hypot(z.re, z.im);\n return { re: z.re / m, im: z.im / m };\n}\n",
74274
74315
  "deep_clone.js": '/* Deep-clone an owned-typed runtime value so a subsequent member /\n * indexed write through one alias doesn\'t leak to others. Mirrors\n * the c-aot per-typedef `_copy` protocol: every Var-RHS Assign /\n * MemberLoad-RHS Assign / function-call arg of an owned type is\n * wrapped through this helper at emit time, so JS-side aliasing\n * stops at the assignment boundary.\n *\n * Recognises the runtime shapes the rest of mtoc2 emits:\n * - RuntimeTensor (`{mtoc2Tag:"tensor", shape, data, imag?, isLogical?}`)\n * - RuntimeChar (`{mtoc2Tag:"char", value}`)\n * - struct / class / handle / handle-capture objects \u2014 plain JS\n * objects, optionally with a non-enumerable `mtoc2Class` tag\n * (class instances) that must survive the clone for method\n * dispatch\n *\n * Primitives (numbers, booleans, strings, null/undefined) flow\n * through unchanged \u2014 they\'re already value-typed in JS.\n */\n\nfunction mtoc2_deep_clone(v) {\n if (v === null || typeof v !== "object") return v;\n if (v.mtoc2Tag === "tensor") {\n const out = {\n mtoc2Tag: "tensor",\n shape: v.shape.slice(),\n data: v.data.slice(),\n };\n if (v.imag !== undefined) out.imag = v.imag.slice();\n if (v.isLogical) out.isLogical = true;\n return out;\n }\n if (v.mtoc2Tag === "char") {\n return { mtoc2Tag: "char", value: v.value };\n }\n if (v.mtoc2Tag === "cell") {\n return {\n mtoc2Tag: "cell",\n shape: v.shape.slice(),\n data: v.data.map(mtoc2_deep_clone),\n };\n }\n if (v.mtoc2Handle === true) {\n // Function handle \u2014 captures are deep-cloned so a later write\n // through one alias doesn\'t bleed into another handle\'s frozen\n // capture snapshot.\n if (v.kind === "named") {\n return { mtoc2Handle: true, kind: "named", name: v.name };\n }\n const captures = {};\n for (const k of Object.keys(v.captures)) {\n captures[k] = mtoc2_deep_clone(v.captures[k]);\n }\n return {\n mtoc2Handle: true,\n kind: "anon",\n params: v.params,\n body: v.body,\n captures,\n };\n }\n // Plain struct / class instance: walk enumerable fields, then\n // restore the non-enumerable `mtoc2Class` tag if it was present.\n const out = {};\n for (const k of Object.keys(v)) {\n out[k] = mtoc2_deep_clone(v[k]);\n }\n const tag = Object.getOwnPropertyDescriptor(v, "mtoc2Class");\n if (tag !== undefined) {\n Object.defineProperty(out, "mtoc2Class", {\n value: tag.value,\n enumerable: false,\n writable: false,\n });\n }\n return out;\n}\n',
74275
74316
  "isequal.js": "// JS sibling of `isequal.h`. Same real-numeric equality semantics for\n// the js-aot backend. Tensors carry `shape` / `data` (the JS analogue\n// of the C struct's `dims` / `real`).\n\nfunction mtoc2_isequal_tt(a, b) {\n if (a.shape.length !== b.shape.length) return 0;\n let n = 1;\n for (let i = 0; i < a.shape.length; i++) {\n if (a.shape[i] !== b.shape[i]) return 0;\n n *= a.shape[i];\n }\n for (let i = 0; i < n; i++) {\n if (a.data[i] !== b.data[i]) return 0;\n }\n return 1;\n}\n\nfunction mtoc2_isequal_st(s, t) {\n let n = 1;\n for (let i = 0; i < t.shape.length; i++) n *= t.shape[i];\n if (n !== 1) return 0;\n return t.data[0] === s ? 1 : 0;\n}\n",
74276
74317
  "tictoc.js": '// JS sibling of `tictoc.h`. Same "one global timer" semantics. Uses\n// `performance.now()` to match numbl\'s wall-clock source; the output\n// format string mirrors numbl\'s `"Elapsed time is %.6f seconds.\\n"`\n// so the cross-runner\'s masking regex still treats both runtimes as\n// equivalent.\n//\n// Module-scope state for the shared `tic` slot. The codegen path\n// inlines this declaration alongside the helpers; the interpreter\n// imports them as ES exports and the same module-scope `let` is the\n// canonical slot.\n\nlet mtoc2_tic_seconds = 0;\n\nfunction mtoc2_now_seconds() {\n // `performance.now()` returns milliseconds since some monotonic\n // epoch; divide for seconds. (Node and browsers both supply it on\n // `globalThis.performance` as of recent versions.)\n return performance.now() / 1000;\n}\n\nfunction mtoc2_tic() {\n mtoc2_tic_seconds = mtoc2_now_seconds();\n return mtoc2_tic_seconds;\n}\n\nfunction mtoc2_toc() {\n return mtoc2_now_seconds() - mtoc2_tic_seconds;\n}\n\nfunction mtoc2_toc_handle(start_seconds) {\n return mtoc2_now_seconds() - start_seconds;\n}\n\nfunction mtoc2_toc_print() {\n const elapsed = mtoc2_toc();\n $write(`Elapsed time is ${elapsed.toFixed(6)} seconds.\\n`);\n}\n\nfunction mtoc2_toc_handle_print(start_seconds) {\n const elapsed = mtoc2_now_seconds() - start_seconds;\n $write(`Elapsed time is ${elapsed.toFixed(6)} seconds.\\n`);\n}\n',
@@ -87018,6 +87059,24 @@ function lowerMultiAssign(s) {
87018
87059
  );
87019
87060
  }
87020
87061
  }
87062
+ if (target?.kind === "mtoc2UserFunction") {
87063
+ const userBuiltin = this.workspace.getUserBuiltin(target.name);
87064
+ if (!userBuiltin) {
87065
+ throw new UnsupportedConstruct(
87066
+ `internal: workspace resolved '${callName}' to a .mtoc2.js function but no Builtin is loaded`,
87067
+ s.span
87068
+ );
87069
+ }
87070
+ return buildBuiltinMultiAssign.call(
87071
+ this,
87072
+ callName,
87073
+ userBuiltin,
87074
+ anfArgs,
87075
+ argTypes,
87076
+ argHoists,
87077
+ s
87078
+ );
87079
+ }
87021
87080
  if (target?.kind !== "userFunction") {
87022
87081
  throw new UnsupportedConstruct(
87023
87082
  `multi-assign of '${callName}': only user-defined functions can appear on the right of '[...] = ...' (or as a bare multi-output statement)`,
@@ -87553,23 +87612,6 @@ function lowerIndexSliceStore(lvalue, exprAst, span) {
87553
87612
  }
87554
87613
  }
87555
87614
  }
87556
- if (!isSingleSlot && r.baseTy.kind === "Numeric") {
87557
- for (let i = 0; i < slots.length; i++) {
87558
- const slot = slots[i];
87559
- if (slot.kind !== "Scalar") continue;
87560
- const dim = r.baseTy.dims[i];
87561
- const t = slot.expr.ty;
87562
- const dimKnown = dim !== void 0 && dim.kind === "exact";
87563
- const valKnown = t.kind === "Numeric" && typeof t.exact === "number";
87564
- const provablyInBounds = dimKnown && valKnown && Math.round(t.exact) >= 1 && Math.round(t.exact) <= dim.value;
87565
- if (!provablyInBounds) {
87566
- throw new UnsupportedConstruct(
87567
- `indexed write to '${displayName}' at axis ${i + 1}: cannot statically prove the index stays within the tensor's bounds; would auto-grow / OOB in MATLAB, but auto-grow is not modeled in the JIT`,
87568
- slot.span
87569
- );
87570
- }
87571
- }
87572
- }
87573
87615
  const rawRhs = this.lowerExpr(exprAst);
87574
87616
  if (!isNumeric2(rawRhs.ty)) {
87575
87617
  throw new TypeError2(
@@ -87885,6 +87927,8 @@ var Lowerer = class {
87885
87927
  return lowerMultiAssign.call(this, s);
87886
87928
  case "If":
87887
87929
  return this.lowerIf(s);
87930
+ case "Switch":
87931
+ return this.lowerSwitch(s);
87888
87932
  case "While":
87889
87933
  return this.lowerWhile(s);
87890
87934
  case "For":
@@ -87976,6 +88020,23 @@ var Lowerer = class {
87976
88020
  return entries;
87977
88021
  }
87978
88022
  lowerExprStmt(s) {
88023
+ if (this.env.get("tic") === void 0 && !this.workspace.isClass("tic")) {
88024
+ const bareTic = s.expr.type === "Ident" && s.expr.name === "tic" || s.expr.type === "FuncCall" && s.expr.name === "tic" && s.expr.args.length === 0;
88025
+ if (bareTic) {
88026
+ return {
88027
+ kind: "ExprStmt",
88028
+ expr: {
88029
+ kind: "Call",
88030
+ cName: "mtoc2_tic",
88031
+ name: "tic",
88032
+ args: [],
88033
+ ty: VOID,
88034
+ span: s.expr.span
88035
+ },
88036
+ span: s.span
88037
+ };
88038
+ }
88039
+ }
87979
88040
  if (this.env.get("toc") === void 0 && !this.workspace.isClass("toc")) {
87980
88041
  if (s.expr.type === "Ident" && s.expr.name === "toc") {
87981
88042
  return {
@@ -88582,6 +88643,125 @@ var Lowerer = class {
88582
88643
  }
88583
88644
  ];
88584
88645
  }
88646
+ /** MATLAB `switch` lowered to the equivalent if/elseif chain.
88647
+ *
88648
+ * The subject is evaluated once — hoisted to a temp unless it is already a
88649
+ * plain variable — and each case label becomes an equality test against it:
88650
+ * `strcmp` for text, `==` for numbers. A `case {a, b}` list is the OR of
88651
+ * the individual tests. Everything after that is `lowerIf`'s problem, which
88652
+ * means switch inherits its branch-env merging and cond folding.
88653
+ *
88654
+ * Declined (leaving the unit to the interpreter, as before this existed):
88655
+ * a subject that is neither a scalar number nor text, a label whose kind
88656
+ * does not match the subject's, and an empty `case {}`.
88657
+ */
88658
+ lowerSwitch(s) {
88659
+ const span = s.span;
88660
+ const pre = [];
88661
+ let subject = s.expr;
88662
+ if (!(s.expr.type === "Ident" && this.env.has(s.expr.name))) {
88663
+ const tmp = this.freshTempName();
88664
+ const assign = {
88665
+ type: "Assign",
88666
+ name: tmp,
88667
+ expr: s.expr,
88668
+ suppressed: true,
88669
+ span
88670
+ };
88671
+ const lowered = this.lowerStmt(assign);
88672
+ if (lowered !== null) {
88673
+ if (Array.isArray(lowered)) pre.push(...lowered);
88674
+ else pre.push(lowered);
88675
+ }
88676
+ subject = { type: "Ident", name: tmp, span };
88677
+ }
88678
+ const subjTy = this.env.get(subject.name)?.ty;
88679
+ if (subjTy === void 0) {
88680
+ throw new UnsupportedConstruct(
88681
+ "switch subject is not a value the JIT can type",
88682
+ span
88683
+ );
88684
+ }
88685
+ const subjIsText = subjTy.kind === "Char" || subjTy.kind === "String";
88686
+ const subjIsNum = isScalarRealNumeric(subjTy) || subjTy.kind === "Numeric";
88687
+ if (!subjIsText && !(subjIsNum && isScalar(subjTy))) {
88688
+ throw new UnsupportedConstruct(
88689
+ `switch on a ${typeToString(subjTy)} subject is not supported (scalar numeric or text only)`,
88690
+ span
88691
+ );
88692
+ }
88693
+ const labelIsText = (label) => {
88694
+ if (label.type === "Char" || label.type === "String") return true;
88695
+ if (label.type === "Ident") {
88696
+ const t = this.env.get(label.name)?.ty;
88697
+ return t !== void 0 && (t.kind === "Char" || t.kind === "String");
88698
+ }
88699
+ return false;
88700
+ };
88701
+ const labelCond = (label) => {
88702
+ if (subjIsText) {
88703
+ if (!labelIsText(label)) {
88704
+ throw new UnsupportedConstruct(
88705
+ "switch on text with a non-text case label is not supported",
88706
+ label.span
88707
+ );
88708
+ }
88709
+ return {
88710
+ type: "FuncCall",
88711
+ name: "strcmp",
88712
+ args: [subject, label],
88713
+ span: label.span
88714
+ };
88715
+ }
88716
+ if (labelIsText(label)) {
88717
+ throw new UnsupportedConstruct(
88718
+ "switch on a number with a text case label is not supported",
88719
+ label.span
88720
+ );
88721
+ }
88722
+ return {
88723
+ type: "Binary",
88724
+ left: subject,
88725
+ op: "Equal" /* Equal */,
88726
+ right: label,
88727
+ span: label.span
88728
+ };
88729
+ };
88730
+ const caseCond = (value) => {
88731
+ if (value.type !== "Cell") return labelCond(value);
88732
+ const items = value.rows.flat();
88733
+ if (items.length === 0) {
88734
+ throw new UnsupportedConstruct(
88735
+ "switch with an empty case list is not supported",
88736
+ value.span
88737
+ );
88738
+ }
88739
+ return items.map(labelCond).reduce((acc, c) => ({
88740
+ type: "Binary",
88741
+ left: acc,
88742
+ op: "OrOr" /* OrOr */,
88743
+ right: c,
88744
+ span: value.span
88745
+ }));
88746
+ };
88747
+ if (s.cases.length === 0) {
88748
+ const body = s.otherwise ? this.lowerStmts(s.otherwise) : [];
88749
+ return [...pre, ...body];
88750
+ }
88751
+ const chain = {
88752
+ type: "If",
88753
+ cond: caseCond(s.cases[0].value),
88754
+ thenBody: s.cases[0].body,
88755
+ elseifBlocks: s.cases.slice(1).map((c) => ({
88756
+ cond: caseCond(c.value),
88757
+ body: c.body
88758
+ })),
88759
+ elseBody: s.otherwise,
88760
+ span
88761
+ };
88762
+ const ir = this.lowerIf(chain);
88763
+ return [...pre, ...Array.isArray(ir) ? ir : [ir]];
88764
+ }
88585
88765
  lowerWhile(s) {
88586
88766
  const envBefore = new Map(this.env);
88587
88767
  stripExactFromEnv(this.env, collectAssignedNames(s.body));
@@ -91415,30 +91595,39 @@ function compileSpec(args) {
91415
91595
  const { workspace, lowerer, funcDecl, argTypes, nargout } = args;
91416
91596
  workspace.finalize();
91417
91597
  const widenedArgTypes = argTypes.map(withoutExact);
91418
- const spec = specializeUserFunction.call(
91419
- lowerer,
91420
- funcDecl,
91421
- widenedArgTypes,
91422
- void 0,
91423
- void 0,
91424
- void 0,
91425
- nargout,
91426
- void 0
91427
- );
91428
- assertNoNonVoidBareExprStmts(spec);
91429
- const prog = {
91430
- topLevelStmts: [],
91431
- functions: new Map(lowerer.specializations)
91432
- };
91433
- const { source, activatedSnippets } = emitJsProgram(prog, {
91434
- workspace,
91435
- exposeSpec: spec.cName
91436
- });
91437
- return {
91438
- cName: spec.cName,
91439
- source,
91440
- activatedSnippets
91441
- };
91598
+ const before = new Set(lowerer.specializations.keys());
91599
+ try {
91600
+ const spec = specializeUserFunction.call(
91601
+ lowerer,
91602
+ funcDecl,
91603
+ widenedArgTypes,
91604
+ void 0,
91605
+ void 0,
91606
+ void 0,
91607
+ nargout,
91608
+ void 0
91609
+ );
91610
+ assertNoNonVoidBareExprStmts(spec);
91611
+ const prog = {
91612
+ topLevelStmts: [],
91613
+ functions: new Map(lowerer.specializations)
91614
+ };
91615
+ const { source, activatedSnippets } = emitJsProgram(prog, {
91616
+ workspace,
91617
+ exposeSpec: spec.cName
91618
+ });
91619
+ return {
91620
+ cName: spec.cName,
91621
+ source,
91622
+ activatedSnippets,
91623
+ outputTypes: spec.outputTypes
91624
+ };
91625
+ } catch (err) {
91626
+ for (const key of lowerer.specializations.keys()) {
91627
+ if (!before.has(key)) lowerer.specializations.delete(key);
91628
+ }
91629
+ throw err;
91630
+ }
91442
91631
  }
91443
91632
  function assertNoNonVoidBareExprStmts(spec) {
91444
91633
  const walk = (stmts) => {
@@ -92489,7 +92678,17 @@ function jitTypeToCompilerType(jt) {
92489
92678
  case "char":
92490
92679
  return { kind: "Char" };
92491
92680
  // Kinds the JIT path doesn't accept yet — fall through to interp.
92492
- case "struct":
92681
+ case "struct": {
92682
+ if (jt.fields === void 0) return null;
92683
+ const names = Object.keys(jt.fields).sort();
92684
+ const fields = [];
92685
+ for (const name of names) {
92686
+ const fty = jitTypeToCompilerType(jt.fields[name]);
92687
+ if (fty === null) return null;
92688
+ fields.push({ name, ty: fty });
92689
+ }
92690
+ return { kind: "Struct", fields };
92691
+ }
92493
92692
  case "struct_array":
92494
92693
  case "class_instance":
92495
92694
  case "sparse_matrix":
@@ -92535,11 +92734,20 @@ function numblToJit(v, paramType) {
92535
92734
  if (isRuntimeComplexNumber(v)) {
92536
92735
  return { re: v.re, im: v.im };
92537
92736
  }
92737
+ if (isRuntimeStruct(v)) {
92738
+ const sty = paramType?.kind === "Struct" ? paramType : void 0;
92739
+ const out = {};
92740
+ for (const [name, fv] of v.fields) {
92741
+ const fty = sty?.fields.find((f) => f.name === name)?.ty;
92742
+ out[name] = numblToJit(fv, fty);
92743
+ }
92744
+ return out;
92745
+ }
92538
92746
  throw new Error(
92539
92747
  `numblToJit: unsupported RuntimeValue (executor should have declined)`
92540
92748
  );
92541
92749
  }
92542
- function jitToNumbl(v) {
92750
+ function jitToNumbl(v, retType) {
92543
92751
  if (typeof v === "number") return v;
92544
92752
  if (typeof v === "boolean") return v;
92545
92753
  if (typeof v === "string") return v;
@@ -92562,6 +92770,14 @@ function jitToNumbl(v) {
92562
92770
  if (typeof tagged.re === "number" && typeof tagged.im === "number") {
92563
92771
  return new RuntimeComplexNumber(tagged.re, tagged.im);
92564
92772
  }
92773
+ if (retType?.kind === "Struct") {
92774
+ const fields = /* @__PURE__ */ new Map();
92775
+ for (const f of retType.fields) {
92776
+ const fv = v[f.name];
92777
+ if (fv !== void 0) fields.set(f.name, jitToNumbl(fv, f.ty));
92778
+ }
92779
+ return new RuntimeStruct(fields);
92780
+ }
92565
92781
  throw new Error(`jitToNumbl: unrecognized return shape`);
92566
92782
  }
92567
92783
  function isGrowBail(e) {
@@ -92635,7 +92851,8 @@ var jitCallExecutor = {
92635
92851
  name: "jit-call",
92636
92852
  propose(lowered, ctx) {
92637
92853
  if (lowered.kind !== "call") return null;
92638
- if (ctx.interp.loopDepth > 0) return null;
92854
+ if (ctx.interp.loopDepth > 0 && ctx.interp.jitDeclinedLoopDepth === 0)
92855
+ return null;
92639
92856
  const classification = lowered.classification;
92640
92857
  if (ctx.interp.optimization === "2" && classification.assertsCJit) {
92641
92858
  return null;
@@ -92667,7 +92884,7 @@ var jitCallExecutor = {
92667
92884
  compile(d, ctx) {
92668
92885
  const { workspace, lowerer } = getOrCreateSession(ctx.interp);
92669
92886
  try {
92670
- const { source, cName } = compileSpec({
92887
+ const { source, cName, outputTypes } = compileSpec({
92671
92888
  workspace,
92672
92889
  lowerer,
92673
92890
  funcDecl: synthesizeFuncStmt(d.fn),
@@ -92683,12 +92900,13 @@ var jitCallExecutor = {
92683
92900
  );
92684
92901
  const factory = new Function(source)();
92685
92902
  const specFn = factory(buildHostHelpers(interp.rt));
92686
- return { specFn };
92903
+ return { specFn, outputTypes };
92687
92904
  } catch (e) {
92688
92905
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
92689
92906
  recordJitDecline({
92690
92907
  message: e.message,
92691
92908
  kind: e.constructor.name,
92909
+ at: e.span ? { file: e.span.file, start: e.span.start } : void 0,
92692
92910
  where: "jit-call"
92693
92911
  });
92694
92912
  return null;
@@ -92711,9 +92929,11 @@ var jitCallExecutor = {
92711
92929
  `jit-call: expected array for nargout=${d.nargout}, got ${typeof result}`
92712
92930
  );
92713
92931
  }
92714
- return { result: result.map((v) => jitToNumbl(v)) };
92932
+ return {
92933
+ result: result.map((v, i) => jitToNumbl(v, compiled.outputTypes[i]))
92934
+ };
92715
92935
  }
92716
- return { result: jitToNumbl(result) };
92936
+ return { result: jitToNumbl(result, compiled.outputTypes[0]) };
92717
92937
  } catch (e) {
92718
92938
  if (isGrowBail(e)) {
92719
92939
  ctx.interp.onJitBail?.(
@@ -92757,7 +92977,8 @@ var jitLoopExecutor = {
92757
92977
  name: "jit-loop",
92758
92978
  propose(lowered, ctx) {
92759
92979
  if (lowered.kind !== "loop") return null;
92760
- if (ctx.interp.loopDepth > 0) return null;
92980
+ if (ctx.interp.loopDepth > 0 && ctx.interp.jitDeclinedLoopDepth === 0)
92981
+ return null;
92761
92982
  const classification = lowered.classification;
92762
92983
  if (ctx.interp.optimization === "2" && classification.assertsCJit) {
92763
92984
  return null;
@@ -92799,7 +93020,7 @@ var jitLoopExecutor = {
92799
93020
  );
92800
93021
  const nargout = d.outputs.length;
92801
93022
  try {
92802
- const { source, cName } = compileSpec({
93023
+ const { source, cName, outputTypes } = compileSpec({
92803
93024
  workspace,
92804
93025
  lowerer,
92805
93026
  funcDecl,
@@ -92814,12 +93035,13 @@ var jitLoopExecutor = {
92814
93035
  );
92815
93036
  const factory = new Function(source)();
92816
93037
  const specFn = factory(buildHostHelpers(interp.rt));
92817
- return { specFn };
93038
+ return { specFn, outputTypes };
92818
93039
  } catch (e) {
92819
93040
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
92820
93041
  recordJitDecline({
92821
93042
  message: e.message,
92822
93043
  kind: e.constructor.name,
93044
+ at: e.span ? { file: e.span.file, start: e.span.start } : void 0,
92823
93045
  where: "jit-loop"
92824
93046
  });
92825
93047
  return null;
@@ -92844,7 +93066,9 @@ var jitLoopExecutor = {
92844
93066
  if (d.outputs.length === 0) {
92845
93067
  } else if (d.outputs.length === 1) {
92846
93068
  if (result !== void 0) {
92847
- const rv = ensureRuntimeValue(jitToNumbl(result));
93069
+ const rv = ensureRuntimeValue(
93070
+ jitToNumbl(result, compiled.outputTypes[0])
93071
+ );
92848
93072
  interp.env.set(d.outputs[0], rv);
92849
93073
  }
92850
93074
  } else {
@@ -92856,7 +93080,9 @@ var jitLoopExecutor = {
92856
93080
  for (let i = 0; i < d.outputs.length; i++) {
92857
93081
  const elt = result[i];
92858
93082
  if (elt !== void 0) {
92859
- const rv = ensureRuntimeValue(jitToNumbl(elt));
93083
+ const rv = ensureRuntimeValue(
93084
+ jitToNumbl(elt, compiled.outputTypes[i])
93085
+ );
92860
93086
  interp.env.set(d.outputs[i], rv);
92861
93087
  }
92862
93088
  }
@@ -92944,7 +93170,7 @@ var jitTopLevelExecutor = {
92944
93170
  );
92945
93171
  const nargout = d.outputs.length;
92946
93172
  try {
92947
- const { source, cName } = compileSpec({
93173
+ const { source, cName, outputTypes } = compileSpec({
92948
93174
  workspace,
92949
93175
  lowerer,
92950
93176
  funcDecl,
@@ -92959,12 +93185,13 @@ var jitTopLevelExecutor = {
92959
93185
  );
92960
93186
  const factory = new Function(source)();
92961
93187
  const specFn = factory(buildHostHelpers(interp.rt));
92962
- return { specFn, nargout };
93188
+ return { specFn, nargout, outputTypes };
92963
93189
  } catch (e) {
92964
93190
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
92965
93191
  recordJitDecline({
92966
93192
  message: e.message,
92967
93193
  kind: e.constructor.name,
93194
+ at: e.span ? { file: e.span.file, start: e.span.start } : void 0,
92968
93195
  where: "jit-top-level"
92969
93196
  });
92970
93197
  return null;
@@ -92989,7 +93216,9 @@ var jitTopLevelExecutor = {
92989
93216
  if (d.outputs.length === 0) {
92990
93217
  } else if (d.outputs.length === 1) {
92991
93218
  if (result !== void 0) {
92992
- const rv = ensureRuntimeValue(jitToNumbl(result));
93219
+ const rv = ensureRuntimeValue(
93220
+ jitToNumbl(result, compiled.outputTypes[0])
93221
+ );
92993
93222
  interp.env.set(d.outputs[0], rv);
92994
93223
  }
92995
93224
  } else {
@@ -93001,7 +93230,9 @@ var jitTopLevelExecutor = {
93001
93230
  for (let i = 0; i < d.outputs.length; i++) {
93002
93231
  const elt = result[i];
93003
93232
  if (elt !== void 0) {
93004
- const rv = ensureRuntimeValue(jitToNumbl(elt));
93233
+ const rv = ensureRuntimeValue(
93234
+ jitToNumbl(elt, compiled.outputTypes[i])
93235
+ );
93005
93236
  interp.env.set(d.outputs[i], rv);
93006
93237
  }
93007
93238
  }