Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,46 @@

### New Features

- **Compiled JavaScript runs without the engine.** The new entry point
`@cortex-js/compute-engine/runtime` exports `createJavaScriptRuntime(options)`,
the `_SYS` helper bundle that `compile()` builds for `run()`, with no engine
behind it: store the `code` of a `JavaScriptTarget` result, and run it on a
page, in a worker or on a server with `runtime.load(result)`. The runtime
type exposes only `load`, `frame`, `iterationLimit`, `deadline` and
`runtimeVersion`; the helpers are internal and may change in any release. The
helpers that read engine state take it as options: `random` is the source of
draws outside any `WithRandomSeed` frame and of the integrals' Monte-Carlo
samples (`null` denies draws, and a draw then throws a `CapabilityDeniedError`,
a different class in each bundle, so test `e.name`, not `instanceof`);
`frame` (or `runtime.setFrame()`) is the frame of an interpreted
`WithRandomSeed` the code is called from (`{ seedLo, seedHi, next }` or
`{ seed, next }`), and `runtime.frame.next` is the advanced counter after the
call; `iterationLimit` caps the lazy-stream
walks (default 1024, and `Infinity` when set to 0 or less, as
`ce.iterationLimit`) and `deadline` is an optional time after which the
shuffle and choice loops throw. A seeded program gives the same values
interpreted, with `run()` and as stored code:
`WithRandomSeed(7, RandomShuffle(Range(1, 6)))` is the same list in all
three. `load()` evaluates the definitions that read nothing per call (a
constant list, a memo) once, as `run()` does, from the new
`CompilationResult.preambleOnce`, `preamblePerCall` and `callCode`, so a
`At(L, Floor(x))` with a 1,000-element `L` no longer rebuilds `L` on every
call. `load()` also applies the input conversions `run()` does, from the new
`CompilationResult.entryPlan`: a real given to a complex-declared symbol is
lifted (`z^2 + z` at `z = 2` is 6 from both, where `load()` gave a complex
NaN), and a `Float64Array` for a list symbol is copied. The digits a negative
base's exponent is read to, `(-2)^x` at `x = 33.3333333333333`, are fixed in
`CompilationResult.reconstructionDigits`, since the runtime's own number
library cannot tell a machine-precision engine from the default (10822639409.68
from `run()`, NaN from the runtime before).
`CompilationResult.runtimeVersion` and `runtime.runtimeVersion` are the
version of the helper set; `load()` throws when they differ or when the
stored code has none. Functions passed in the `functions` or `imports`
compile options are copied into the code as source (`toString()`), so a
closure loses its enclosing scope and a function given by name must exist
where the code runs. The engine's own `_SYS` (`run.SYS`) is built from the
same factory. (#372, contributed by [enumeratio](https://github.com/enumeratio))

- **`ResidueClass(k, n)`: an element of ℤ/nℤ** (#399, #411, contributed by
[enumeratio](https://github.com/enumeratio)). A residue class is now a
value.
Expand Down
37 changes: 37 additions & 0 deletions docs/COMPILATION-MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,43 @@ divergences are recorded in `ROADMAP.md`.
A consumer that needs tolerant equality evaluates through the interpreter, or
asks for a compile option; none exists today.

## Running stored JavaScript

The code of a JavaScript compilation result reads only `_SYS` and `_`.
`@cortex-js/compute-engine/runtime` exports `createJavaScriptRuntime(options)`,
the `_SYS` that `run()` uses, with no engine behind it, so code compiled in one
place (a worker, a build) runs in another with only the numerics it calls. Its
type exposes `load`, `frame` (set with `setFrame`), `iterationLimit`, `deadline`
and `runtimeVersion`;
the helper table is internal and may change in any release. `load()` evaluates
the definitions that read nothing per call (`preambleOnce`) once, as `run()`
does, and the rest (`preamblePerCall`) on every call, and applies the input
conversions of `run()` from `entryPlan` (a real given to a complex-declared
symbol is lifted, a typed array for a list symbol is copied, a complex value for
a real symbol is refused). The
engine-bound helpers take their state as options: `random` (draws outside a
`WithRandomSeed` frame and the integrals' Monte-Carlo samples; `null` denies),
`frame` (the enclosing frame, set with `setFrame()` and read back from
`runtime.frame.next`),
`iterationLimit` (`Infinity` when 0 or less) and `deadline`. A denied draw
throws a `CapabilityDeniedError`, a different class in each bundle: test
`e.name`, not `instanceof`. Tolerance, angular unit and the digits a negative
base's float exponent is read to (`reconstructionDigits`, 15 to 17 by the
engine's precision) are fixed at compile time, and compiled code always uses
machine floats.

The result and the runtime both carry `runtimeVersion`; a mismatch means the
code was compiled by another release, and `runtime.load()` refuses it, as it
refuses code with no `runtimeVersion`.

Functions passed in the `functions` or `imports` compile options are copied into
the code as source (`toString()`): a closure loses its enclosing scope, and a
function given by name must exist where the code runs.

`compilation/javascript-runtime.ts` holds the helpers and imports no engine code
(`test/compute-engine/js-runtime.test.ts` bundles the entry point and checks
it). `run.SYS` is built from the same factory.

## Target boundaries

The JavaScript target supports the broadest dynamic representation. Python,
Expand Down
38 changes: 24 additions & 14 deletions docs/RANDOMNESS-MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,21 +526,31 @@ Dynamic scoping has to survive compilation, or it silently becomes lexical at th
boundary: a compiled body would ignore the frame it is running inside and draw
live instead.

**The binding is an engine reference, not a frame handle.** There is exactly one
frame slot per engine, and both the interpreter and compiled code reach it
through the engine — one representation, not two kept in agreement.

- `makeSysHelpers(ce)` (`compilation/javascript-target.ts`) builds each compiled
artifact's `_SYS` bundle **over the compiling engine**.
- `_SYS.drawNextRandomNumber()` delegates to `ce._random()` — the *same*
primitive the interpreter uses. It resolves the engine's active frame **at call
time**, advances that frame's counter, and returns `hash(seed, n)`; with no
frame it draws from the `entropy` handler of the host capability registry
(`ce.effects.entropy`, `Math.random` by default — `docs/EFFECTS-MODEL.md`,
"Host capabilities").
**The binding is a source, not a frame handle.** There is exactly one frame
slot per source (per engine, for the engine's), and both the interpreter and
compiled code reach it through it — one representation, not two kept in
agreement.

- `makeSysHelpers(source)` (`compilation/javascript-runtime.ts`) builds each
compiled artifact's `_SYS` bundle over a `RuntimeSource`: the live draw, the
frame slot, the iteration limit and the deadline. The engine passes its own
(`ce._liveRandom`, `ce._randomFrame`, `ce.iterationLimit`,
`ce._deadlineFrame`); `createJavaScriptRuntime` (`@cortex-js/compute-engine/runtime`)
passes plain options, so the same helpers run with no engine.
- `_SYS.drawNextRandomNumber()` is `nextFrameDraw` on the active frame, the
*same* primitive `ce._random()` uses. It resolves the frame **at call time**,
advances that frame's counter, and returns `hash(seed, n)`; with no frame it
draws from the source's live draw, which for the engine is the `entropy`
handler of the host capability registry (`ce.effects.entropy`, `Math.random`
by default — `docs/EFFECTS-MODEL.md`, "Host capabilities"), and for a
runtime its `random` option (`null` denies the draw, which then throws).
- Compiled `WithRandomSeed` emits `_SYS.withRandomSeed(seed, () => body)`, which
is literally `withRandomSeedFrame(ce, seed, …)` — the same push/`finally`-pop
as the interpreter, on the same slot.
is `withSeedFrame` over the source's frame slot — the same push/`finally`-pop
as the interpreter (`withRandomSeedFrame`).
- A runtime takes an outer frame as input (`frame`, `{ seedLo, seedHi, next }`
or `{ seed, next }`) and keeps the advanced counter in `runtime.frame.next`,
so a host that runs the code elsewhere sends the frame there and reads the
counter back.

One mechanism therefore covers all three cases:

Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"postpublish": "bash ./scripts/package-vsix.sh",
"test": "bash ./scripts/test.sh",
"test:nodenext": "node ./test/consumer/nodenext-smoke.mjs",
"test:js-runtime": "node ./test/consumer/js-runtime-smoke.mjs",
"test:epsil-runtime": "node ./test/consumer/epsil-runtime-smoke.mjs",
"test:epsil-cli": "node ./test/consumer/epsil-cli-smoke.mjs",
"test:nightly": "CE_NIGHTLY=1 jest --config ./config/jest.config.cjs --reporters default -- test/compute-engine/nightly/",
Expand Down Expand Up @@ -103,6 +104,12 @@
"require": "./dist/umd-min/compile.cjs",
"default": "./dist/esm-min/compile.js"
},
"./runtime": {
"types": "./dist/types/runtime.d.ts",
"import": "./dist/esm-min/runtime.js",
"require": "./dist/umd-min/runtime.cjs",
"default": "./dist/esm-min/runtime.js"
},
"./identities": {
"types": "./dist/types/identities.d.ts",
"import": "./dist/esm-min/identities.js",
Expand Down
17 changes: 17 additions & 0 deletions scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ const COMPILE_UMD_OPTIONS = {
},
};

const RUNTIME_UMD_OPTIONS = {
banner: {
js: `/** Runtime ${SDK_VERSION} ${
process.env.GIT_VERSION ? ' -- ' + process.env.GIT_VERSION : ''
}*/
(function(global,factory){typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'],factory):(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ComputeEngineRuntime = {}));})(this, (function (exports) { 'use strict';`,
},
footer: {
js: `Object.assign(exports, ComputeEngineRuntime); Object.defineProperty(exports, '__esModule', { value: true });}));`,
},
};

const IDENTITIES_UMD_OPTIONS = {
banner: {
js: `/** Identities ${SDK_VERSION} ${
Expand Down Expand Up @@ -180,6 +192,11 @@ const ENTRIES = [
{ name: 'numerics', umd: NUMERICS_UMD_OPTIONS, globalName: 'Numerics' },
{ name: 'core', umd: CORE_UMD_OPTIONS, globalName: 'ComputeEngineCore' },
{ name: 'compile', umd: COMPILE_UMD_OPTIONS, globalName: 'Compile' },
{
name: 'runtime',
umd: RUNTIME_UMD_OPTIONS,
globalName: 'ComputeEngineRuntime',
},
{ name: 'identities', umd: IDENTITIES_UMD_OPTIONS, globalName: 'Identities' },
{
name: 'integration-rules',
Expand Down
10 changes: 9 additions & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fi
export BUILD="${1-production}"

# export TARGETS="math-json epsil compute-engine"
export TARGETS="math-json latex-syntax interval numerics core compile identities integration-rules epsil compute-engine"
export TARGETS="math-json latex-syntax interval numerics core compile runtime identities integration-rules epsil compute-engine"

# export GIT_VERSION=`git describe --long --dirty`

Expand Down Expand Up @@ -124,6 +124,10 @@ if [[ "$TARGETS" == *compile* ]]; then
"$TS7" $DTS_FLAGS \
--emitDeclarationOnly --outDir ./dist/types ./src/compile.ts
fi
if [[ "$TARGETS" == *runtime* ]]; then
"$TS7" $DTS_FLAGS \
--emitDeclarationOnly --outDir ./dist/types ./src/runtime.ts
fi
if [[ "$TARGETS" == *identities* ]]; then
# --resolveJsonModule/--esModuleInterop: the identities entry imports the
# compiled rule artifact (fungrim-core-data.json); these flags are not in
Expand Down Expand Up @@ -182,6 +186,10 @@ if [ "$BUILD" = "production" ]; then
node ./test/consumer/epsil-runtime-smoke.mjs
echo -e $LINECLEAR$BASENAME$CHECK$DIM" Verifying Epsil runtime smoke$RESET"

printf "$BASENAME$DOT Verifying JavaScript runtime smoke"
node ./test/consumer/js-runtime-smoke.mjs
echo -e $LINECLEAR$BASENAME$CHECK$DIM" Verifying JavaScript runtime smoke$RESET"

printf "$BASENAME$DOT Verifying Epsil CLI smoke"
node ./test/consumer/epsil-cli-smoke.mjs
echo -e $LINECLEAR$BASENAME$CHECK$DIM" Verifying Epsil CLI smoke$RESET"
Expand Down
4 changes: 4 additions & 0 deletions src/common/interruptible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,7 @@ await new Promise((resolve) => setTimeout(resolve, 2000));


*/

/** The default cap on iterations of a loop or lazy walk (`ce.iterationLimit`,
* and the `iterationLimit` of a compiled-code runtime). */
export const DEFAULT_ITERATION_LIMIT = 1024;
Loading
Loading