@pulse-compute/wasm-compiler 1.0.0-beta.4 → 1.0.0-beta.6

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.
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ // Native plan owner for shared, transfer-capable HTTP route and middleware stages. Admission
4
+ // is conservative: an unsupported shape keeps the ordinary Native lowering.
5
+ function loadStageContract() {
6
+ try { return require('@pulse-compute/wasm-contracts/handler/canonical-native-plan'); }
7
+ catch (error) {
8
+ if (['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../contracts/src/handler/canonical-native-plan');
9
+ throw error;
10
+ }
11
+ }
12
+ const contract = loadStageContract().CANONICAL_NATIVE_STAGE_CONTRACT;
13
+ const VERSION = contract.version;
14
+ const OUTPUTS = contract.outputs;
15
+ const MAX_SITES = contract.maxEffectSites;
16
+ const excluded = Symbol('not a shared stage');
17
+ const demand = (condition, reason = 'unsupported-shape') => {
18
+ if (!condition) throw { [excluded]: true, reason };
19
+ };
20
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
21
+ function mapTree(node, replace) {
22
+ const replaced = replace(node);
23
+ if (replaced !== node) return replaced;
24
+ if (Array.isArray(node)) return node.map(value => mapTree(value, replace));
25
+ if (!node || typeof node !== 'object') return node;
26
+ return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, mapTree(value, replace)]));
27
+ }
28
+ function statements(body, visit, loopDepth = 0) {
29
+ for (const statement of body || []) {
30
+ visit(statement, loopDepth);
31
+ if (statement.kind === 'if') { statements(statement.then, visit, loopDepth); statements(statement.else, visit, loopDepth); }
32
+ else if (statement.kind === 'pure-loop') statements(statement.body, visit, loopDepth + 1);
33
+ }
34
+ }
35
+ function effectShape(effect) {
36
+ return Object.fromEntries(['kind', 'providerKind', 'operation', 'capability', 'resource', 'package', 'contractId',
37
+ 'declaredResult', 'borrowedValue', 'grouped', 'groupIndex', 'decoder', 'inputs', 'result'].map(key => [key, effect[key]]));
38
+ }
39
+ function eligibleEffect(effect) {
40
+ return effect && !effect.grouped && effect.groupIndex === undefined && !effect.helperId && !effect.borrowedValue
41
+ && effect.result?.mode === 'bind' && (effect.kind === 'fetch' && effect.decoder === 'text'
42
+ || ['time.now', 'crypto.digestText'].includes(effect.kind) && effect.decoder === null && !effect.result.decoder);
43
+ }
44
+ function rowFor(plan, entry, router) {
45
+ demand(['route', 'use'].includes(entry.kind) && !entry.nativeBody, 'entry-kind');
46
+ const outer = plan.entry.body.find(s => s.kind === 'if' && s.test?.kind === 'binary'
47
+ && s.test.operator === '===' && s.test.left?.id === router.cursor && s.test.right?.value === entry.index);
48
+ demand(outer?.then?.length === 1 && outer.then[0].kind === 'if');
49
+ const branch = outer.then[0], body = branch.then, localIds = [], effectIds = [];
50
+ statements(body, (s, loopDepth) => {
51
+ demand(s.kind !== 'helper-call', 'nested-helper-call');
52
+ demand((loopDepth ? ['local', 'expression', 'if', 'pure-loop', 'break', 'continue']
53
+ : ['local', 'expression', 'return', 'if', 'effect', 'pure-loop']).includes(s.kind), 'unsupported-statement:' + s.kind);
54
+ if (s.kind === 'local' || s.kind === 'pure-loop') localIds.push(s.localId);
55
+ if (s.kind === 'effect') {
56
+ demand(s.result?.mode === 'bind', 'effect-result-mode'); localIds.push(s.result.localId); effectIds.push(s.effectId);
57
+ }
58
+ });
59
+ demand(new Set(localIds).size === localIds.length && effectIds.length > 0 && effectIds.length <= MAX_SITES, 'locals-or-effect-count');
60
+ const effects = effectIds.map(id => plan.effects.find(e => e.id === id));
61
+ demand(effects.every(e => e?.routerEntryStableId === entry.stableId && eligibleEffect(e)), 'effect-kind-or-ownership');
62
+ const locals = new Map(localIds.map((id, i) => [id, `local-${i}`]));
63
+ const ids = new Map(effects.flatMap((e, i) => [[e.id, `effect-${i}`], [e.continuationId, `continuation-${i}`]]));
64
+ let transfers = 0;
65
+ function normalize(node) {
66
+ if (Array.isArray(node)) return node.map(normalize);
67
+ if (!node || typeof node !== 'object') return node;
68
+ // Helper ownership is still tied to the original Router entry, not a stage.
69
+ demand(node.kind !== 'pure-helper-call' && node.kind !== 'helper-call', 'nested-helper-call');
70
+ if (node.kind === 'local' && node.id) demand(locals.has(node.id) || Object.values(router).includes(node.id));
71
+ if (node.kind === 'assignment' && node.target?.id === router.cursor && node.value !== null) {
72
+ demand(node.operator === '=' && node.value?.kind === 'literal' && node.value.value === entry.nextIndex);
73
+ transfers++;
74
+ return { ...normalize({ ...node, value: null }), value: { kind: 'stage-next-cursor' } };
75
+ }
76
+ return Object.fromEntries(Object.entries(node).filter(([key]) => !['statementPath', 'localName'].includes(key)
77
+ && !(key === 'name' && node.kind === 'local')).map(([key, value]) => [key,
78
+ ['id', 'localId'].includes(key) && locals.has(value) ? locals.get(value)
79
+ : ['effectId', 'continuationId'].includes(key) && ids.has(value) ? ids.get(value) : normalize(value)]));
80
+ }
81
+ const signature = JSON.stringify({ body: normalize(body), effects: effects.map(e => normalize(effectShape(e))) });
82
+ demand(transfers > 0);
83
+ return { entry, branch, body, localIds, effects, signature };
84
+ }
85
+
86
+ function lowerSharedStages(plan, { onExcluded } = {}) {
87
+ const router = Object.fromEntries(['cursor', 'mode', 'error'].map(name => [name,
88
+ plan.locals.find(local => local.name === `__pulse_router_${name}`)?.id]));
89
+ if (!Object.values(router).every(Boolean)) return plan;
90
+ const families = new Map();
91
+ for (const entry of plan.routing?.entries || []) {
92
+ if (!families.has(entry.handlerId)) families.set(entry.handlerId, []);
93
+ families.get(entry.handlerId).push(entry);
94
+ }
95
+ const continuationOwners = new Map();
96
+ const stages = [], replacement = new Map(), localOwners = new Map(), removedLocals = new Set(), effectReplacements = new Map();
97
+ for (const entries of families.values()) {
98
+ let rows;
99
+ try {
100
+ rows = entries.map(entry => rowFor(plan, entry, router));
101
+ demand(rows.every(row => row.signature === rows[0].signature), 'registration-shape-mismatch');
102
+ } catch (error) {
103
+ if (!error?.[excluded]) throw error;
104
+ // Optional internal inspection; never changes the valid fallback plan/hash.
105
+ if (onExcluded && entries[0].handlerId) onExcluded({
106
+ handlerId: entries[0].handlerId, entryIds: entries.map(entry => entry.stableId), reason: error.reason
107
+ });
108
+ continue;
109
+ }
110
+ const first = rows[0], id = `stage:${first.entry.stableId}`, nextCursorLocalId = `${id}:next`;
111
+ const body = mapTree(first.body, node => node?.kind === 'assignment' && node.target?.id === router.cursor
112
+ ? { ...node, value: { kind: 'local', id: nextCursorLocalId, valueKind: 'number' } } : node);
113
+ const registrations = rows.map(row => {
114
+ replacement.set(row.branch, { ...row.branch, then: [{ kind: 'stage-call', stageId: id, registrationId: row.entry.stableId }] });
115
+ row.localIds.forEach(local => removedLocals.add(local));
116
+ row.effects.forEach((effect, site) => {
117
+ effectReplacements.set(effect.id, { ...effect, stageId: id, stageSite: site, inputs: first.effects[site].inputs, result: first.effects[site].result });
118
+ continuationOwners.set(effect.continuationId, { stageId: id, stageSite: site });
119
+ });
120
+ return { entryId: row.entry.stableId, nextIndex: row.entry.nextIndex,
121
+ effectIds: row.effects.map(e => e.id), continuationIds: row.effects.map(e => e.continuationId) };
122
+ });
123
+ first.localIds.forEach(local => { removedLocals.delete(local); localOwners.set(local, id); });
124
+ stages.push({ version: VERSION, id, handlerId: first.entry.handlerId,
125
+ inputs: { context: 'request', nextCursorLocalId, routerLocals: router }, outputs: OUTPUTS,
126
+ frame: contract.frame,
127
+ localIds: first.localIds, effectIds: first.effects.map(e => e.id), body, registrations });
128
+ }
129
+ if (!stages.length) return plan;
130
+ const rewriteBranches = body => body.map(s => replacement.get(s) || (s.kind === 'if'
131
+ ? { ...s, then: rewriteBranches(s.then), else: rewriteBranches(s.else) } : s));
132
+ const locals = plan.locals.filter(local => !removedLocals.has(local.id)).map(local => localOwners.has(local.id)
133
+ ? { ...local, scopeId: localOwners.get(local.id) } : local);
134
+ for (const stage of stages) locals.push({ id: stage.inputs.nextCursorLocalId, scopeId: stage.id, name: '__pulse_stage_next_cursor', valueKind: 'number' });
135
+ return { ...plan, stages, entry: { ...plan.entry, body: rewriteBranches(plan.entry.body) }, locals,
136
+ effects: plan.effects.map(e => effectReplacements.get(e.id) || e),
137
+ continuations: plan.continuations.map(c => continuationOwners.has(c.id) ? { ...c, ...continuationOwners.get(c.id) } : c) };
138
+ }
139
+
140
+ function validateSharedStages(plan, fail, walkStatements, walkExpression, stableStringify) {
141
+ if (plan.stages !== undefined && !Array.isArray(plan.stages)) { fail('stages must be an array'); return; }
142
+ const stages = new Map(), bindings = new Map(), calls = new Map();
143
+ const entries = new Map((plan.routing?.entries || []).map(e => [e.stableId, e]));
144
+ const effects = new Map(plan.effects.map(e => [e.id, e]));
145
+ const continuations = new Map(plan.continuations.map(c => [c.id, c]));
146
+ const locals = new Map(plan.locals.map(l => [l.id, l]));
147
+ for (const stage of plan.stages || []) {
148
+ if (!stage || typeof stage.id !== 'string' || !stage.id.startsWith('stage:')
149
+ || (plan.handlers || []).some(handler => handler.id === stage.id) || stages.has(stage.id) || stage.version !== VERSION || !Array.isArray(stage.body)
150
+ || !Array.isArray(stage.localIds) || !Array.isArray(stage.effectIds) || !Array.isArray(stage.registrations)
151
+ || !stage.inputs || !same(stage.outputs, OUTPUTS)
152
+ || !same(stage.frame, contract.frame)) {
153
+ fail('shared stage contract is invalid'); continue;
154
+ }
155
+ stages.set(stage.id, stage);
156
+ const router = stage.inputs.routerLocals || {};
157
+ if (stage.inputs.context !== 'request' || !['cursor', 'mode', 'error'].every(name =>
158
+ locals.get(router[name])?.name === `__pulse_router_${name}` && locals.get(router[name])?.scopeId === 'entry')) fail('stage requires explicit request and Router inputs');
159
+ const own = new Set([...stage.localIds, stage.inputs.nextCursorLocalId]);
160
+ if (own.size !== stage.localIds.length + 1 || [...own].some(id => locals.get(id)?.scopeId !== stage.id)
161
+ || plan.locals.filter(l => l.scopeId === stage.id).length !== own.size
162
+ || locals.get(stage.inputs.nextCursorLocalId)?.valueKind !== 'number') fail('stage local ownership is invalid');
163
+ const allowed = new Set([...own, ...Object.values(router)]);
164
+ const observedEffects = [], declaredLocals = [];
165
+ const visitor = s => {
166
+ // The common plan validator enforces pure-loop caps, purity and transfers;
167
+ // this visitor also discovers induction and nested-body local ownership.
168
+ if (!['local', 'expression', 'return', 'if', 'effect', 'pure-loop', 'break', 'continue'].includes(s.kind)) fail('shared stage contains unsupported control flow');
169
+ if (s.kind === 'local' || s.kind === 'pure-loop') declaredLocals.push(s.localId);
170
+ if (s.kind === 'effect') {
171
+ observedEffects.push(s.effectId); declaredLocals.push(s.result?.localId);
172
+ const effect = effects.get(s.effectId);
173
+ if (!effect || s.continuationId !== effect.continuationId || !same(s.result, effect.result)) fail('stage statement effect binding is invalid');
174
+ }
175
+ if (s.localId && !own.has(s.localId)) fail('stage statement crosses local ownership');
176
+ };
177
+ visitor.expression = expr => {
178
+ if (expr.kind === 'pure-helper-call') fail('shared stage contains unsupported helper call');
179
+ if (expr.kind === 'local' && !allowed.has(expr.id)) fail('stage captures another lexical owner');
180
+ if (['assignment', 'update'].includes(expr.kind) && expr.target?.id === stage.inputs.nextCursorLocalId) fail('stage return input is read-only');
181
+ if (expr.kind === 'assignment' && expr.target?.id === router.cursor
182
+ && (expr.operator !== '=' || expr.value?.kind !== 'local' || expr.value.id !== stage.inputs.nextCursorLocalId)) fail('stage transfer must use its selected return cursor');
183
+ };
184
+ walkStatements(stage.body, visitor);
185
+ if (!same(observedEffects, stage.effectIds) || !same(declaredLocals, stage.localIds)
186
+ || !stage.effectIds.length || stage.effectIds.length > MAX_SITES) fail('stage sites or locals do not match its body');
187
+ const first = stage.registrations[0];
188
+ if (!first || !same(stage.effectIds, first.effectIds)) fail('stage requires a representative registration');
189
+ for (const row of stage.registrations) {
190
+ const entry = entries.get(row?.entryId);
191
+ if (!row || bindings.has(row.entryId) || !['route', 'use'].includes(entry?.kind) || entry.nativeBody
192
+ || entry.handlerId !== stage.handlerId || row.nextIndex !== entry.nextIndex
193
+ || !Array.isArray(row.effectIds) || !Array.isArray(row.continuationIds)
194
+ || row.effectIds.length !== stage.effectIds.length || row.continuationIds.length !== stage.effectIds.length) {
195
+ fail('stage registration ownership is invalid'); continue;
196
+ }
197
+ bindings.set(row.entryId, stage.id);
198
+ row.effectIds.forEach((id, site) => {
199
+ const effect = effects.get(id), representative = effects.get(stage.effectIds[site]);
200
+ const continuation = continuations.get(row.continuationIds[site]);
201
+ if (!effect || effect.stageId !== stage.id || effect.stageSite !== site || effect.routerEntryStableId !== row.entryId
202
+ || !eligibleEffect(effect)
203
+ || effect.continuationId !== row.continuationIds[site] || continuation?.routerEntryStableId !== row.entryId
204
+ || continuation?.stageId !== stage.id || continuation?.stageSite !== site
205
+ || !same(continuation?.effectIds, [id]) || !representative
206
+ || stableStringify(effectShape(effect)) !== stableStringify(effectShape(representative))) fail('stage effect/continuation binding is invalid');
207
+ for (const input of effect?.inputs || []) walkExpression(input.value, visitor.expression);
208
+ for (const argument of effect?.result?.decoder?.arguments || []) walkExpression(argument, visitor.expression);
209
+ if (!own.has(effect?.result?.localId)) fail('stage result crosses local ownership');
210
+ });
211
+ }
212
+ }
213
+ // Calls may occur only in the matching registration's admitted branch.
214
+ function checkCalls(body, entryId, admission = false) {
215
+ for (const s of body || []) {
216
+ if (s.kind === 'stage-call') {
217
+ if (!stages.has(s.stageId) || !admission || entryId !== s.registrationId || bindings.get(s.registrationId) !== s.stageId) fail('stage call is outside its registration admission');
218
+ calls.set(s.registrationId, (calls.get(s.registrationId) || 0) + 1);
219
+ }
220
+ if (s.kind === 'if') {
221
+ const entry = s.test?.kind === 'binary' && s.test.operator === '===' && s.test.left?.kind === 'local'
222
+ && locals.get(s.test.left.id)?.name === '__pulse_router_cursor'
223
+ && [...entries.values()].find(e => e.index === s.test.right?.value);
224
+ checkCalls(s.then, entry ? entry.stableId : entryId, !entry && Boolean(entryId));
225
+ checkCalls(s.else, undefined);
226
+ }
227
+ }
228
+ }
229
+ checkCalls(plan.entry?.body);
230
+ for (const id of bindings.keys()) if (calls.get(id) !== 1) fail('stage registration requires one static call');
231
+ for (const effect of plan.effects) if (effect.stageId !== undefined
232
+ && (bindings.get(effect.routerEntryStableId) !== effect.stageId
233
+ || !stages.get(effect.stageId)?.registrations.some(row => row.effectIds[effect.stageSite] === effect.id))) fail('orphan stage effect binding');
234
+ for (const continuation of plan.continuations) if (continuation.stageId !== undefined
235
+ && (bindings.get(continuation.routerEntryStableId) !== continuation.stageId
236
+ || !stages.get(continuation.stageId)?.registrations.some(row => row.continuationIds[continuation.stageSite] === continuation.id))) fail('orphan stage continuation binding');
237
+ return { stages, bindings };
238
+ }
239
+ module.exports = { VERSION, OUTPUTS, MAX_SITES, lowerSharedStages, validateSharedStages };