compiler-worker.js (5976B)
1 // Test-only JS evaluator driver; excluded from the production site. 2 import { compileExpression } from './expression.js'; 3 import { environmentProfile } from '../packages.js'; 4 import { BROWSER_BUDGET, collectionPolicy } from '../execution.js'; 5 import { contractEnvironment } from '../contracts.js'; 6 import { Graph } from '../graph.js'; 7 import { COMPILER_BUNDLE_LIMIT, fetchBundle, fetchBytes, loadBundle } from '../bundle.js'; 8 import { literals, makeLiteral, outputType, view, list, text } from '../codecs.js'; 9 import { COMPILER_EXPORT, compilerDescriptor, sourceLiteral, sourceText, compiledDefinitions } from '../compiler.js'; 10 const scope = globalThis; 11 const base = new URL('../../', import.meta.url); 12 const send = (message) => scope.postMessage(message); 13 const running = () => send({ type: 'state', state: 'running' }); 14 let phase = 'empty'; 15 let graph, exports; 16 async function compile(source, profileName) { 17 phase = 'compiling'; 18 running(); 19 const start = performance.now(); 20 const budget = BROWSER_BUDGET; 21 graph = new Graph(budget, collectionPolicy(budget)); 22 const input = sourceLiteral(graph, source); // Validate before downloading a compiler. 23 const bytes = await fetchBytes(new URL('compiler.json', base), 4096); 24 const raw = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)); 25 const { hash } = compilerDescriptor(raw); 26 const bundle = await fetchBundle(new URL(`/_arboricx/bundle/hash/${hash}`, base), hash, COMPILER_BUNDLE_LIMIT); 27 const library = loadBundle(graph, bundle), compiler = library.get(COMPILER_EXPORT); 28 if (compiler === undefined) 29 throw new Error('Missing stable compiler entrypoint'); 30 let environment; 31 if (profileName === undefined) 32 environment = contractEnvironment(graph, library); 33 else { 34 const bytes = await fetchBytes(new URL('environments.json', base), 32_768); 35 const selected = environmentProfile(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)), profileName); 36 if (selected.compiler !== hash) 37 throw new Error('Demo environment/compiler pin mismatch'); 38 const bindings = []; 39 if (selected.profile) { 40 const support = await fetchBundle(new URL(`/_arboricx/bundle/hash/${selected.profile.hash}`, base), selected.profile.hash); 41 const values = loadBundle(graph, support); 42 for (const name of selected.profile.names) { 43 const tree = values.get(name); 44 if (tree === undefined) 45 throw new Error(`Missing demo environment binding ${name}`); 46 bindings.push(graph.fork(text(graph, name), tree)); 47 } 48 } 49 environment = list(graph, bindings); 50 } 51 exports = compiledDefinitions(graph, graph.apply(compiler, [environment, input])); 52 graph.collect(exports.values()); // Safe phase boundary; compiler/input temporaries are no longer caller roots. 53 phase = 'ready'; 54 send({ type: 'compiled', names: [...exports.keys()], draft: { graph: graph.checkpoint(), exports: [...exports] }, stats: graph.stats, milliseconds: performance.now() - start }); 55 } 56 async function run(message) { 57 if (!graph || !exports) 58 throw new Error('Compile a unit first'); 59 phase = 'running'; 60 running(); 61 const fn = message.expression === undefined ? exports.get(message.export) : await compileExpression(graph, exports, message.expression); 62 if (fn === undefined) 63 throw new Error('Unknown compiled definition'); 64 if (message.mode !== 'pure' && message.mode !== 'io') 65 throw new Error('Unsupported execution mode'); 66 const type = outputType(message.output), args = literals(message.arguments).map(value => makeLiteral(graph, value)); 67 let result = message.expression === undefined ? graph.apply(fn, args) : fn; 68 if (message.mode === 'io') 69 throw new Error('Reference graph IO execution is retired; use a native runtime'); 70 const displayed = view(graph, result, type); 71 graph.collect(exports.values()); // Drop this run's temporaries, retaining every definition. 72 phase = 'ready'; 73 send({ type: 'done', result: displayed, stats: graph.stats }); 74 } 75 scope.onmessage = ({ data }) => { 76 if (!data || typeof data !== 'object' || Array.isArray(data)) 77 return; 78 const message = data; 79 let operation; 80 try { 81 if (message['type'] === 'restore' && phase === 'empty') { 82 // Only the owning conformance harness supplies this trusted in-memory checkpoint. 83 const restored = data; 84 const budget = BROWSER_BUDGET; 85 graph = Graph.restore(restored.draft.graph, budget, collectionPolicy(budget)); 86 exports = new Map(restored.draft.exports); 87 phase = 'ready'; 88 return; 89 } 90 else if (message['type'] === 'compile' && phase === 'empty') { 91 if (message['environment'] !== undefined && (typeof message['environment'] !== 'string' || message['environment'].length > 128)) 92 throw new Error('Invalid demo environment'); 93 operation = compile(message['source'], message['environment']); 94 } 95 else if (message['type'] === 'run' && phase === 'ready') { 96 if (typeof message['export'] !== 'string' || message['export'].length > 128) 97 throw new Error('Invalid compiled selection'); 98 operation = run({ type: 'run', export: message['export'], arguments: message['arguments'], 99 ...(message['expression'] === undefined ? {} : { expression: sourceText(message['expression']) }), 100 output: outputType(message['output']), mode: message['mode'], step: message['step'] === true }); 101 } 102 else 103 return; 104 } 105 catch (error) { 106 operation = Promise.reject(error); 107 } 108 operation.catch((error) => { phase = 'done'; send({ type: 'error', message: error instanceof Error ? error.message : String(error) }); }); 109 };