Skip to content

This site describes solve-engine as it is on main: 2.43.0, which npm does not have yet. npm installs 2.40.0, so a page may show an answer that version does not give yet.

VMConfig

Defined in: packages/engine/src/constants/Configuration.ts:353

Virtual Machine configuration. Controls the internal bytecode VM that executes compiled expressions.

readonly maxAllocatedElements: number;

Defined in: packages/engine/src/constants/Configuration.ts:391

Maximum elements (collection Values, matrix cells) one evaluation may materialize in total.

maxCollectionSize above bounds a single collection. This bounds the sum of everything an expression asks for, which is a different question and the one that actually protects the host: two collections that are individually legal are legal together, and an operation whose result is the PRODUCT of two legal operands is bounded by neither of them. A matrix multiply is exactly that shape, so three lines within every other limit (:a = map(1*x, 0:20000), :b = transpose(a), b * a) asked for four hundred million cells and aborted the process.

Counted in elements rather than bytes, because a count is what a call site has before it allocates. An element is 8 bytes as a numeric matrix cell and closer to a hundred as a full Value, so the default is worth roughly 16 MB of matrix or 200 MB of expanded collection: far past any document and far short of what an editor cannot survive.


readonly maxCollectionSize: number;

Defined in: packages/engine/src/constants/Configuration.ts:371

Maximum elements a collection may be expanded to before map/reduce/ sum/prod will iterate it.

A Range is stored as its two bounds and costs nothing until something materializes it, at which point it becomes one Value per element. Twenty characters (sum(x, 1:100000000)) therefore asked for a hundred million of them, and neither maxInstructions nor maxStackDepth could see it: the expansion happens inside a single opcode, so the instruction counter is never consulted while it runs, and the elements never reach the value stack. V8 aborted the whole process with “Reached heap limit”, which a host embedding the engine cannot catch.


readonly maxFunctionCalls: number;

Defined in: packages/engine/src/constants/Configuration.ts:411

Maximum user-defined-function calls one evaluation may make in TOTAL, however deeply or widely they nest.

maxFunctionRecursionDepth bounds how DEEP calls nest and cannot see how MANY there are, and those are different numbers. Twenty-two lines of f(n)(v) = f(n-1)(v) + f(n-1)(v) reach a depth of only 22 against a limit of 50, and make 2,097,152 calls doing it: a fatal heap abort in under a second. maxInstructions cannot bound it either, because executeBytecode() re-enters itself per call and each reentrant call gets its OWN instruction count, so recursion refreshes its allowance on the way in. This is the tally that does not refresh; see vm/AllocationBudget.ts, which holds it for the same reason it holds the element tally.

Counted in calls rather than in the instructions they run, because the call is the thing that multiplies: every call allocates a frame, its arguments and its result whatever its body says.


readonly maxGoalSeekIterations: number;

Defined in: packages/engine/src/constants/Configuration.ts:426

Hard ceiling on how many bisection steps goal seek (solve line N for <var> = <target>, see packages/goalseek/) may take before it gives up with a structured error rather than continuing.

Goal seek re-evaluates the target line’s expression once per step while narrowing an interval, and an untrusted document must never be able to make that loop spin. maxInstructions bounds each individual re-evaluation, but not how many of them the search performs, so this is the tally that bounds the search itself, the same reason maxFunctionCalls exists alongside maxInstructions. Bisection halves the interval every step, so a hundred steps resolves an input to roughly one part in 2^100: far past any precision a document needs, and a definite stop either way.


readonly maxInstructions: number;

Defined in: packages/engine/src/constants/Configuration.ts:357

Maximum opcodes executed per expression, halts runaway infinite loops


readonly maxLineRunsPerPass: number;

Defined in: packages/engine/src/constants/Configuration.ts:450

Hard ceiling on the work one pass over a document may do across its lines, counted in line runs (#711).

Every other limit here counts work inside one expression. The forms that reach across lines were capped one line at a time: a sweep at 100,000 line re-runs, goal seek at maxGoalSeekIterations probes, a span aggregate (total above, a section, a tag, a table column) not at all. Twenty sweep lines, each inside its own cap, made one parseDocument re-run 2,000,000 lines, and a host re-parses on every keystroke.

One count per pass: a what-if or sweep charges each line it re-runs, a goal-seek probe one, and a span aggregate the lines it reads at sixteen to a run (see vm/PassWork.ts, which measured the rate). The line whose work would cross this is refused with PASS_WORK_BUDGET_EXCEEDED, and the lines above keep their answers.

A million line runs is about five seconds of work on the machine it was measured on (a re-run costs about 5.2 µs there): ten sweeps at their own cap, or a ledger with a running total after each of about 4,000 entries. A document that needs more is extreme, and the setting says how to allow it.


readonly maxRetainedElements: number;

Defined in: packages/engine/src/constants/Configuration.ts:471

Hard ceiling on the elements a document keeps in its lines’ answers, counted each pass (#694): a list or matrix its cells, text one element to eight characters, any other answer one.

maxAllocatedElements bounds what one evaluation may create, and nothing bounded what a document keeps across its lines: 500 lines of :m = map(10*x, 0:99999), 13 KB of text, kept 50,000,000 elements and 385 MB of heap, every line inside every per-line limit.

The line whose answer would take the document past this is refused with DOCUMENT_ELEMENT_LIMIT_EXCEEDED, and a name it assigned is let go, so the value is not kept through a variable either. The lines above keep their answers. The count starts again on every pass, so an edited or deleted line’s answer stops counting when it does.

Ten million is five lines at maxAllocatedElements, or a hundred 100,000-element lists: about 80 MB of plain numbers, measured at about 8 bytes an element.


readonly maxStackDepth: number;

Defined in: packages/engine/src/constants/Configuration.ts:355

Maximum stack depth (value slots) for VM execution, prevents stack overflow in recursive/pratt-parser generated bytecode