numbl 0.4.17 → 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.
- package/dist-browser/browser.js +1 -1
- package/dist-browser/numbl-core/executors/jit/callExecutor.d.ts +7 -1
- package/dist-browser/numbl-core/executors/jit/loopExecutor.d.ts +12 -7
- package/dist-browser/numbl-core/executors/jit/topLevelExecutor.d.ts +3 -0
- package/dist-browser/numbl-core/executors/jit/valueAdapter.d.ts +9 -2
- package/dist-browser/numbl-core/executors/registry.d.ts +8 -0
- package/dist-browser/numbl-core/interpreter/interpreter.d.ts +11 -0
- package/dist-browser/numbl-core/jit/builtins/runtime/system/cscalar.d.ts +4 -4
- package/dist-browser/numbl-core/jit/codegen/emitIndex.d.ts +1 -1
- package/dist-browser/numbl-core/jit/compileSpec.d.ts +4 -0
- package/dist-browser/numbl-core/jit/lowering/lower.d.ts +13 -0
- package/dist-browser/numbl-core/jitDeclineDiagnostics.d.ts +6 -0
- package/dist-browser/numbl-core/version.d.ts +1 -1
- package/dist-cli/cli.js +370 -110
- package/dist-lib/lib.js +307 -68
- package/dist-lib/numbl-core/executors/jit/callExecutor.d.ts +7 -1
- package/dist-lib/numbl-core/executors/jit/loopExecutor.d.ts +12 -7
- package/dist-lib/numbl-core/executors/jit/topLevelExecutor.d.ts +3 -0
- package/dist-lib/numbl-core/executors/jit/valueAdapter.d.ts +9 -2
- package/dist-lib/numbl-core/executors/registry.d.ts +8 -0
- package/dist-lib/numbl-core/interpreter/interpreter.d.ts +11 -0
- package/dist-lib/numbl-core/jit/builtins/runtime/system/cscalar.d.ts +4 -4
- package/dist-lib/numbl-core/jit/codegen/emitIndex.d.ts +1 -1
- package/dist-lib/numbl-core/jit/compileSpec.d.ts +4 -0
- package/dist-lib/numbl-core/jit/lowering/lower.d.ts +13 -0
- package/dist-lib/numbl-core/jitDeclineDiagnostics.d.ts +6 -0
- package/dist-lib/numbl-core/version.d.ts +1 -1
- package/dist-site-viewer/assets/{index-0mP47Rpm.js → index-B5Y5557F.js} +259 -259
- package/dist-site-viewer/assets/{numbl-worker-DjOZZaW4.js → numbl-worker-CtoT7C0f.js} +104 -88
- package/dist-site-viewer/index.html +1 -1
- package/dist-site-viewer/og.png +0 -0
- package/dist-site-viewer/robots.txt +4 -0
- package/package.json +3 -2
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 {
|
|
@@ -30240,6 +30240,9 @@ var ExpressionParser = class extends ParserBase {
|
|
|
30240
30240
|
if (this.peekToken() === 50 /* LParen */ && this.inMatrixExpr && this.pos > 0 && !this.tokensAdjacent(this.pos - 1, this.pos)) {
|
|
30241
30241
|
break;
|
|
30242
30242
|
}
|
|
30243
|
+
if (this.peekToken() === 54 /* LBrace */ && this.inMatrixExpr && this.pos > 0 && !this.tokensAdjacent(this.pos - 1, this.pos)) {
|
|
30244
|
+
break;
|
|
30245
|
+
}
|
|
30243
30246
|
if (this.consume(50 /* LParen */)) {
|
|
30244
30247
|
const start = expr.span.start;
|
|
30245
30248
|
const args = [];
|
|
@@ -48808,8 +48811,9 @@ defineBuiltin({
|
|
|
48808
48811
|
cases: [
|
|
48809
48812
|
{
|
|
48810
48813
|
match: (argTypes) => argTypes.length === 0 ? [{ kind: "number" }] : null,
|
|
48811
|
-
apply: () => {
|
|
48814
|
+
apply: (_args, nargout) => {
|
|
48812
48815
|
ticTime = performance.now();
|
|
48816
|
+
if (nargout === 0) return void 0;
|
|
48813
48817
|
return RTV.num(ticTime / 1e3);
|
|
48814
48818
|
}
|
|
48815
48819
|
}
|
|
@@ -56863,6 +56867,7 @@ function registerSpecialBuiltins(rt) {
|
|
|
56863
56867
|
if (na === 0) {
|
|
56864
56868
|
rt.output(`Elapsed time is ${elapsed.toFixed(6)} seconds.
|
|
56865
56869
|
`);
|
|
56870
|
+
return void 0;
|
|
56866
56871
|
}
|
|
56867
56872
|
return RTV.num(elapsed);
|
|
56868
56873
|
}
|
|
@@ -65575,8 +65580,7 @@ var Registry = class {
|
|
|
65575
65580
|
const stmt = siblings[i];
|
|
65576
65581
|
const lowered = tryLower(siblings, i, ctx.interp, this.loweringCache);
|
|
65577
65582
|
if (lowered === null) {
|
|
65578
|
-
|
|
65579
|
-
return { signal: signal2 };
|
|
65583
|
+
return this.interpretFallback(ctx, stmt);
|
|
65580
65584
|
}
|
|
65581
65585
|
let bestExec = null;
|
|
65582
65586
|
let bestData = null;
|
|
@@ -65625,8 +65629,25 @@ var Registry = class {
|
|
|
65625
65629
|
}
|
|
65626
65630
|
}
|
|
65627
65631
|
}
|
|
65628
|
-
|
|
65629
|
-
|
|
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) };
|
|
65630
65651
|
}
|
|
65631
65652
|
runStmtCandidate(stmt, ctx, executor, data, lowered) {
|
|
65632
65653
|
const result = this.executeCandidate(ctx, executor, stmt, data, stmt);
|
|
@@ -65862,6 +65883,10 @@ __export(interpreterExec_exports, {
|
|
|
65862
65883
|
var lastDecline = null;
|
|
65863
65884
|
function recordJitDecline(d) {
|
|
65864
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
|
+
}
|
|
65865
65890
|
}
|
|
65866
65891
|
function getLastJitDecline() {
|
|
65867
65892
|
return lastDecline;
|
|
@@ -67028,7 +67053,12 @@ function evalLValueBase(base, defaultVal) {
|
|
|
67028
67053
|
}
|
|
67029
67054
|
const indices = this.evalIndicesWithEnd(baseVal, base.indices);
|
|
67030
67055
|
try {
|
|
67031
|
-
|
|
67056
|
+
const result = this.rt.indexCell(baseVal, indices);
|
|
67057
|
+
if (Array.isArray(result)) {
|
|
67058
|
+
if (result.length === 1) return result[0];
|
|
67059
|
+
if (result.length === 0) return defaultVal;
|
|
67060
|
+
}
|
|
67061
|
+
return result;
|
|
67032
67062
|
} catch {
|
|
67033
67063
|
return defaultVal;
|
|
67034
67064
|
}
|
|
@@ -67845,6 +67875,7 @@ function interpretWorkspaceFunction(target, args, nargout) {
|
|
|
67845
67875
|
fn = funcDefFromStmt(stmt);
|
|
67846
67876
|
break;
|
|
67847
67877
|
}
|
|
67878
|
+
if (stmt.type !== "ClassDef" && stmt.type !== "Import") break;
|
|
67848
67879
|
}
|
|
67849
67880
|
}
|
|
67850
67881
|
}
|
|
@@ -68644,6 +68675,17 @@ var Interpreter = class {
|
|
|
68644
68675
|
* interpreter never sees them, so this gate only fires on calls
|
|
68645
68676
|
* that genuinely happen at the interpreter level inside a loop. */
|
|
68646
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;
|
|
68647
68689
|
/** @internal Number of enclosing conditional blocks (`if` / `switch` /
|
|
68648
68690
|
* `try`) whose bodies the interpreter is currently executing. Used by
|
|
68649
68691
|
* the loop classifier: when a loop is dispatched with `condBlockDepth >
|
|
@@ -71829,8 +71871,12 @@ function mtoc2_cpow(a, b) {
|
|
|
71829
71871
|
}
|
|
71830
71872
|
return cexp2(mtoc2_cmul(b, clog2(a)));
|
|
71831
71873
|
}
|
|
71874
|
+
function nanOnRealAxis(z) {
|
|
71875
|
+
return z.im === 0 && Number.isNaN(z.re);
|
|
71876
|
+
}
|
|
71832
71877
|
function mtoc2_csqrt(z) {
|
|
71833
71878
|
if (z.im === 0) {
|
|
71879
|
+
if (Number.isNaN(z.re)) return { re: z.re, im: 0 };
|
|
71834
71880
|
if (z.re >= 0) return { re: Math.sqrt(z.re), im: 0 };
|
|
71835
71881
|
return { re: 0, im: Math.sqrt(-z.re) };
|
|
71836
71882
|
}
|
|
@@ -71844,14 +71890,17 @@ function mtoc2_cexp(z) {
|
|
|
71844
71890
|
return cexp2(z);
|
|
71845
71891
|
}
|
|
71846
71892
|
function mtoc2_clog(z) {
|
|
71893
|
+
if (nanOnRealAxis(z)) return { re: z.re, im: 0 };
|
|
71847
71894
|
return clog2(z);
|
|
71848
71895
|
}
|
|
71849
71896
|
function mtoc2_clog2(z) {
|
|
71897
|
+
if (nanOnRealAxis(z)) return { re: z.re, im: 0 };
|
|
71850
71898
|
const l = clog2(z);
|
|
71851
71899
|
const lg2 = Math.log(2);
|
|
71852
71900
|
return { re: l.re / lg2, im: l.im / lg2 };
|
|
71853
71901
|
}
|
|
71854
71902
|
function mtoc2_clog10(z) {
|
|
71903
|
+
if (nanOnRealAxis(z)) return { re: z.re, im: 0 };
|
|
71855
71904
|
const l = clog2(z);
|
|
71856
71905
|
const lg10 = Math.log(10);
|
|
71857
71906
|
return { re: l.re / lg10, im: l.im / lg10 };
|
|
@@ -74262,7 +74311,7 @@ var JS_SNIPPETS = {
|
|
|
74262
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',
|
|
74263
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',
|
|
74264
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",
|
|
74265
|
-
"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",
|
|
74266
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',
|
|
74267
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",
|
|
74268
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',
|
|
@@ -87010,6 +87059,24 @@ function lowerMultiAssign(s) {
|
|
|
87010
87059
|
);
|
|
87011
87060
|
}
|
|
87012
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
|
+
}
|
|
87013
87080
|
if (target?.kind !== "userFunction") {
|
|
87014
87081
|
throw new UnsupportedConstruct(
|
|
87015
87082
|
`multi-assign of '${callName}': only user-defined functions can appear on the right of '[...] = ...' (or as a bare multi-output statement)`,
|
|
@@ -87545,23 +87612,6 @@ function lowerIndexSliceStore(lvalue, exprAst, span) {
|
|
|
87545
87612
|
}
|
|
87546
87613
|
}
|
|
87547
87614
|
}
|
|
87548
|
-
if (!isSingleSlot && r.baseTy.kind === "Numeric") {
|
|
87549
|
-
for (let i = 0; i < slots.length; i++) {
|
|
87550
|
-
const slot = slots[i];
|
|
87551
|
-
if (slot.kind !== "Scalar") continue;
|
|
87552
|
-
const dim = r.baseTy.dims[i];
|
|
87553
|
-
const t = slot.expr.ty;
|
|
87554
|
-
const dimKnown = dim !== void 0 && dim.kind === "exact";
|
|
87555
|
-
const valKnown = t.kind === "Numeric" && typeof t.exact === "number";
|
|
87556
|
-
const provablyInBounds = dimKnown && valKnown && Math.round(t.exact) >= 1 && Math.round(t.exact) <= dim.value;
|
|
87557
|
-
if (!provablyInBounds) {
|
|
87558
|
-
throw new UnsupportedConstruct(
|
|
87559
|
-
`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`,
|
|
87560
|
-
slot.span
|
|
87561
|
-
);
|
|
87562
|
-
}
|
|
87563
|
-
}
|
|
87564
|
-
}
|
|
87565
87615
|
const rawRhs = this.lowerExpr(exprAst);
|
|
87566
87616
|
if (!isNumeric2(rawRhs.ty)) {
|
|
87567
87617
|
throw new TypeError2(
|
|
@@ -87877,6 +87927,8 @@ var Lowerer = class {
|
|
|
87877
87927
|
return lowerMultiAssign.call(this, s);
|
|
87878
87928
|
case "If":
|
|
87879
87929
|
return this.lowerIf(s);
|
|
87930
|
+
case "Switch":
|
|
87931
|
+
return this.lowerSwitch(s);
|
|
87880
87932
|
case "While":
|
|
87881
87933
|
return this.lowerWhile(s);
|
|
87882
87934
|
case "For":
|
|
@@ -87968,6 +88020,23 @@ var Lowerer = class {
|
|
|
87968
88020
|
return entries;
|
|
87969
88021
|
}
|
|
87970
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
|
+
}
|
|
87971
88040
|
if (this.env.get("toc") === void 0 && !this.workspace.isClass("toc")) {
|
|
87972
88041
|
if (s.expr.type === "Ident" && s.expr.name === "toc") {
|
|
87973
88042
|
return {
|
|
@@ -88574,6 +88643,125 @@ var Lowerer = class {
|
|
|
88574
88643
|
}
|
|
88575
88644
|
];
|
|
88576
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
|
+
}
|
|
88577
88765
|
lowerWhile(s) {
|
|
88578
88766
|
const envBefore = new Map(this.env);
|
|
88579
88767
|
stripExactFromEnv(this.env, collectAssignedNames(s.body));
|
|
@@ -91407,30 +91595,39 @@ function compileSpec(args) {
|
|
|
91407
91595
|
const { workspace, lowerer, funcDecl, argTypes, nargout } = args;
|
|
91408
91596
|
workspace.finalize();
|
|
91409
91597
|
const widenedArgTypes = argTypes.map(withoutExact);
|
|
91410
|
-
const
|
|
91411
|
-
|
|
91412
|
-
|
|
91413
|
-
|
|
91414
|
-
|
|
91415
|
-
|
|
91416
|
-
|
|
91417
|
-
|
|
91418
|
-
|
|
91419
|
-
|
|
91420
|
-
|
|
91421
|
-
|
|
91422
|
-
|
|
91423
|
-
|
|
91424
|
-
|
|
91425
|
-
|
|
91426
|
-
|
|
91427
|
-
|
|
91428
|
-
|
|
91429
|
-
|
|
91430
|
-
|
|
91431
|
-
|
|
91432
|
-
|
|
91433
|
-
|
|
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
|
+
}
|
|
91434
91631
|
}
|
|
91435
91632
|
function assertNoNonVoidBareExprStmts(spec) {
|
|
91436
91633
|
const walk = (stmts) => {
|
|
@@ -92481,7 +92678,17 @@ function jitTypeToCompilerType(jt) {
|
|
|
92481
92678
|
case "char":
|
|
92482
92679
|
return { kind: "Char" };
|
|
92483
92680
|
// Kinds the JIT path doesn't accept yet — fall through to interp.
|
|
92484
|
-
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
|
+
}
|
|
92485
92692
|
case "struct_array":
|
|
92486
92693
|
case "class_instance":
|
|
92487
92694
|
case "sparse_matrix":
|
|
@@ -92527,11 +92734,20 @@ function numblToJit(v, paramType) {
|
|
|
92527
92734
|
if (isRuntimeComplexNumber(v)) {
|
|
92528
92735
|
return { re: v.re, im: v.im };
|
|
92529
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
|
+
}
|
|
92530
92746
|
throw new Error(
|
|
92531
92747
|
`numblToJit: unsupported RuntimeValue (executor should have declined)`
|
|
92532
92748
|
);
|
|
92533
92749
|
}
|
|
92534
|
-
function jitToNumbl(v) {
|
|
92750
|
+
function jitToNumbl(v, retType) {
|
|
92535
92751
|
if (typeof v === "number") return v;
|
|
92536
92752
|
if (typeof v === "boolean") return v;
|
|
92537
92753
|
if (typeof v === "string") return v;
|
|
@@ -92554,6 +92770,14 @@ function jitToNumbl(v) {
|
|
|
92554
92770
|
if (typeof tagged.re === "number" && typeof tagged.im === "number") {
|
|
92555
92771
|
return new RuntimeComplexNumber(tagged.re, tagged.im);
|
|
92556
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
|
+
}
|
|
92557
92781
|
throw new Error(`jitToNumbl: unrecognized return shape`);
|
|
92558
92782
|
}
|
|
92559
92783
|
function isGrowBail(e) {
|
|
@@ -92627,7 +92851,8 @@ var jitCallExecutor = {
|
|
|
92627
92851
|
name: "jit-call",
|
|
92628
92852
|
propose(lowered, ctx) {
|
|
92629
92853
|
if (lowered.kind !== "call") return null;
|
|
92630
|
-
if (ctx.interp.loopDepth > 0
|
|
92854
|
+
if (ctx.interp.loopDepth > 0 && ctx.interp.jitDeclinedLoopDepth === 0)
|
|
92855
|
+
return null;
|
|
92631
92856
|
const classification = lowered.classification;
|
|
92632
92857
|
if (ctx.interp.optimization === "2" && classification.assertsCJit) {
|
|
92633
92858
|
return null;
|
|
@@ -92659,7 +92884,7 @@ var jitCallExecutor = {
|
|
|
92659
92884
|
compile(d, ctx) {
|
|
92660
92885
|
const { workspace, lowerer } = getOrCreateSession(ctx.interp);
|
|
92661
92886
|
try {
|
|
92662
|
-
const { source, cName } = compileSpec({
|
|
92887
|
+
const { source, cName, outputTypes } = compileSpec({
|
|
92663
92888
|
workspace,
|
|
92664
92889
|
lowerer,
|
|
92665
92890
|
funcDecl: synthesizeFuncStmt(d.fn),
|
|
@@ -92675,12 +92900,13 @@ var jitCallExecutor = {
|
|
|
92675
92900
|
);
|
|
92676
92901
|
const factory = new Function(source)();
|
|
92677
92902
|
const specFn = factory(buildHostHelpers(interp.rt));
|
|
92678
|
-
return { specFn };
|
|
92903
|
+
return { specFn, outputTypes };
|
|
92679
92904
|
} catch (e) {
|
|
92680
92905
|
if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
|
|
92681
92906
|
recordJitDecline({
|
|
92682
92907
|
message: e.message,
|
|
92683
92908
|
kind: e.constructor.name,
|
|
92909
|
+
at: e.span ? { file: e.span.file, start: e.span.start } : void 0,
|
|
92684
92910
|
where: "jit-call"
|
|
92685
92911
|
});
|
|
92686
92912
|
return null;
|
|
@@ -92703,9 +92929,11 @@ var jitCallExecutor = {
|
|
|
92703
92929
|
`jit-call: expected array for nargout=${d.nargout}, got ${typeof result}`
|
|
92704
92930
|
);
|
|
92705
92931
|
}
|
|
92706
|
-
return {
|
|
92932
|
+
return {
|
|
92933
|
+
result: result.map((v, i) => jitToNumbl(v, compiled.outputTypes[i]))
|
|
92934
|
+
};
|
|
92707
92935
|
}
|
|
92708
|
-
return { result: jitToNumbl(result) };
|
|
92936
|
+
return { result: jitToNumbl(result, compiled.outputTypes[0]) };
|
|
92709
92937
|
} catch (e) {
|
|
92710
92938
|
if (isGrowBail(e)) {
|
|
92711
92939
|
ctx.interp.onJitBail?.(
|
|
@@ -92749,7 +92977,8 @@ var jitLoopExecutor = {
|
|
|
92749
92977
|
name: "jit-loop",
|
|
92750
92978
|
propose(lowered, ctx) {
|
|
92751
92979
|
if (lowered.kind !== "loop") return null;
|
|
92752
|
-
if (ctx.interp.loopDepth > 0
|
|
92980
|
+
if (ctx.interp.loopDepth > 0 && ctx.interp.jitDeclinedLoopDepth === 0)
|
|
92981
|
+
return null;
|
|
92753
92982
|
const classification = lowered.classification;
|
|
92754
92983
|
if (ctx.interp.optimization === "2" && classification.assertsCJit) {
|
|
92755
92984
|
return null;
|
|
@@ -92791,7 +93020,7 @@ var jitLoopExecutor = {
|
|
|
92791
93020
|
);
|
|
92792
93021
|
const nargout = d.outputs.length;
|
|
92793
93022
|
try {
|
|
92794
|
-
const { source, cName } = compileSpec({
|
|
93023
|
+
const { source, cName, outputTypes } = compileSpec({
|
|
92795
93024
|
workspace,
|
|
92796
93025
|
lowerer,
|
|
92797
93026
|
funcDecl,
|
|
@@ -92806,12 +93035,13 @@ var jitLoopExecutor = {
|
|
|
92806
93035
|
);
|
|
92807
93036
|
const factory = new Function(source)();
|
|
92808
93037
|
const specFn = factory(buildHostHelpers(interp.rt));
|
|
92809
|
-
return { specFn };
|
|
93038
|
+
return { specFn, outputTypes };
|
|
92810
93039
|
} catch (e) {
|
|
92811
93040
|
if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
|
|
92812
93041
|
recordJitDecline({
|
|
92813
93042
|
message: e.message,
|
|
92814
93043
|
kind: e.constructor.name,
|
|
93044
|
+
at: e.span ? { file: e.span.file, start: e.span.start } : void 0,
|
|
92815
93045
|
where: "jit-loop"
|
|
92816
93046
|
});
|
|
92817
93047
|
return null;
|
|
@@ -92836,7 +93066,9 @@ var jitLoopExecutor = {
|
|
|
92836
93066
|
if (d.outputs.length === 0) {
|
|
92837
93067
|
} else if (d.outputs.length === 1) {
|
|
92838
93068
|
if (result !== void 0) {
|
|
92839
|
-
const rv = ensureRuntimeValue(
|
|
93069
|
+
const rv = ensureRuntimeValue(
|
|
93070
|
+
jitToNumbl(result, compiled.outputTypes[0])
|
|
93071
|
+
);
|
|
92840
93072
|
interp.env.set(d.outputs[0], rv);
|
|
92841
93073
|
}
|
|
92842
93074
|
} else {
|
|
@@ -92848,7 +93080,9 @@ var jitLoopExecutor = {
|
|
|
92848
93080
|
for (let i = 0; i < d.outputs.length; i++) {
|
|
92849
93081
|
const elt = result[i];
|
|
92850
93082
|
if (elt !== void 0) {
|
|
92851
|
-
const rv = ensureRuntimeValue(
|
|
93083
|
+
const rv = ensureRuntimeValue(
|
|
93084
|
+
jitToNumbl(elt, compiled.outputTypes[i])
|
|
93085
|
+
);
|
|
92852
93086
|
interp.env.set(d.outputs[i], rv);
|
|
92853
93087
|
}
|
|
92854
93088
|
}
|
|
@@ -92936,7 +93170,7 @@ var jitTopLevelExecutor = {
|
|
|
92936
93170
|
);
|
|
92937
93171
|
const nargout = d.outputs.length;
|
|
92938
93172
|
try {
|
|
92939
|
-
const { source, cName } = compileSpec({
|
|
93173
|
+
const { source, cName, outputTypes } = compileSpec({
|
|
92940
93174
|
workspace,
|
|
92941
93175
|
lowerer,
|
|
92942
93176
|
funcDecl,
|
|
@@ -92951,12 +93185,13 @@ var jitTopLevelExecutor = {
|
|
|
92951
93185
|
);
|
|
92952
93186
|
const factory = new Function(source)();
|
|
92953
93187
|
const specFn = factory(buildHostHelpers(interp.rt));
|
|
92954
|
-
return { specFn, nargout };
|
|
93188
|
+
return { specFn, nargout, outputTypes };
|
|
92955
93189
|
} catch (e) {
|
|
92956
93190
|
if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
|
|
92957
93191
|
recordJitDecline({
|
|
92958
93192
|
message: e.message,
|
|
92959
93193
|
kind: e.constructor.name,
|
|
93194
|
+
at: e.span ? { file: e.span.file, start: e.span.start } : void 0,
|
|
92960
93195
|
where: "jit-top-level"
|
|
92961
93196
|
});
|
|
92962
93197
|
return null;
|
|
@@ -92981,7 +93216,9 @@ var jitTopLevelExecutor = {
|
|
|
92981
93216
|
if (d.outputs.length === 0) {
|
|
92982
93217
|
} else if (d.outputs.length === 1) {
|
|
92983
93218
|
if (result !== void 0) {
|
|
92984
|
-
const rv = ensureRuntimeValue(
|
|
93219
|
+
const rv = ensureRuntimeValue(
|
|
93220
|
+
jitToNumbl(result, compiled.outputTypes[0])
|
|
93221
|
+
);
|
|
92985
93222
|
interp.env.set(d.outputs[0], rv);
|
|
92986
93223
|
}
|
|
92987
93224
|
} else {
|
|
@@ -92993,7 +93230,9 @@ var jitTopLevelExecutor = {
|
|
|
92993
93230
|
for (let i = 0; i < d.outputs.length; i++) {
|
|
92994
93231
|
const elt = result[i];
|
|
92995
93232
|
if (elt !== void 0) {
|
|
92996
|
-
const rv = ensureRuntimeValue(
|
|
93233
|
+
const rv = ensureRuntimeValue(
|
|
93234
|
+
jitToNumbl(elt, compiled.outputTypes[i])
|
|
93235
|
+
);
|
|
92997
93236
|
interp.env.set(d.outputs[i], rv);
|
|
92998
93237
|
}
|
|
92999
93238
|
}
|