@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10
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/README.md +10 -10
- package/dist/api/board-types.d.ts +1 -1
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +595 -0
- package/dist/api/shared/framework-manifest.js +193 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +764 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +119 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +263 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +372 -30
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +207 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +39 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +61 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +15 -8
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +122 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Framework manifest validator
|
|
3
|
+
//
|
|
4
|
+
// Cross-checks each manifest claim against the loaded PlatformStrategy,
|
|
5
|
+
// Toolchain, and module exports. Errors are plain data (not thrown) so the
|
|
6
|
+
// validator can be called from tests, a future CLI, or agents reading errors
|
|
7
|
+
// programmatically.
|
|
8
|
+
//
|
|
9
|
+
// Categories A-H are documented in docs/framework-manifest-error-codes.md.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { POLYFILL_BACKED_OPS } from './framework-manifest.js';
|
|
14
|
+
import { HAL_OPERATION_KINDS } from './hal-op-ir.js';
|
|
15
|
+
import { DISPLAY_OPERATION_KINDS } from './display-op-ir.js';
|
|
16
|
+
class Accumulator {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.errors = [];
|
|
19
|
+
this.warnings = [];
|
|
20
|
+
}
|
|
21
|
+
error(code, path, message, subject) {
|
|
22
|
+
this.errors.push({ code, path, message, subject });
|
|
23
|
+
}
|
|
24
|
+
warning(code, path, message, subject) {
|
|
25
|
+
this.warnings.push({ code, path, message, subject });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Map each HAL category to the prefix its op kinds share.
|
|
29
|
+
const CATEGORY_PREFIXES = {
|
|
30
|
+
gpio: ['gpio.'],
|
|
31
|
+
pwm: ['pwm.'],
|
|
32
|
+
rmt: ['rmt.'],
|
|
33
|
+
adc: ['adc.'],
|
|
34
|
+
dac: ['dac.'],
|
|
35
|
+
interrupts: ['interrupt.'],
|
|
36
|
+
tone: ['tone.'],
|
|
37
|
+
timing: ['timing.'],
|
|
38
|
+
power: ['power.'],
|
|
39
|
+
i2c: ['i2c.'],
|
|
40
|
+
spi: ['spi.'],
|
|
41
|
+
uart: ['uart.'],
|
|
42
|
+
pulse: ['pulse.'],
|
|
43
|
+
shift: ['shift.'],
|
|
44
|
+
board: ['board.'],
|
|
45
|
+
wdt: ['wdt.'],
|
|
46
|
+
wifi: ['wifi.'],
|
|
47
|
+
http: ['http.'],
|
|
48
|
+
display: ['display.'],
|
|
49
|
+
preferences: ['preferences.'],
|
|
50
|
+
ble: ['ble.'],
|
|
51
|
+
random: ['random.'],
|
|
52
|
+
fs: ['fs.'],
|
|
53
|
+
mdns: ['mdns.'],
|
|
54
|
+
mqtt: ['mqtt.'],
|
|
55
|
+
ota: ['ota.'],
|
|
56
|
+
temp: ['temp.'],
|
|
57
|
+
hwtimer: ['hwtimer.'],
|
|
58
|
+
capacitive: ['capacitive.'],
|
|
59
|
+
// Unimplemented-but-recognized categories: each framework may declare these
|
|
60
|
+
// as unsupported in its manifest so the coverage matrix records them as a
|
|
61
|
+
// roadmap. The validator probes the resolver and confirms it does NOT lower
|
|
62
|
+
// them (consistent with the 'unsupported' status).
|
|
63
|
+
i2s: ['i2s.'],
|
|
64
|
+
twai: ['twai.'],
|
|
65
|
+
usb: ['usb.'],
|
|
66
|
+
eth: ['eth.'],
|
|
67
|
+
espnow: ['espnow.'],
|
|
68
|
+
crypto: ['crypto.'],
|
|
69
|
+
pcnt: ['pcnt.'],
|
|
70
|
+
mcpwm: ['mcpwm.'],
|
|
71
|
+
// Worker offload + snprintf raw-escape: recognized categories. Frameworks
|
|
72
|
+
// that lower worker.* declare it supported; snprintf.emit is a raw escape
|
|
73
|
+
// hatch (declared unsupported by frameworks that don't special-case it).
|
|
74
|
+
worker: ['worker.'],
|
|
75
|
+
snprintf: ['snprintf.'],
|
|
76
|
+
};
|
|
77
|
+
function opKindsForCategory(category) {
|
|
78
|
+
const prefixes = CATEGORY_PREFIXES[category] ?? [];
|
|
79
|
+
const all = [...HAL_OPERATION_KINDS, ...DISPLAY_OPERATION_KINDS];
|
|
80
|
+
return all.filter((k) => prefixes.some((p) => k.startsWith(p)));
|
|
81
|
+
}
|
|
82
|
+
// Minimal valid payloads for op kinds whose resolver requires more than just
|
|
83
|
+
// the operation discriminator. The default probe (just `{ operation: kind }`)
|
|
84
|
+
// works for most ops; this map fills in the smallest payload that lets the
|
|
85
|
+
// resolver run without throwing on missing-arg validation.
|
|
86
|
+
//
|
|
87
|
+
// Templates must be the MINIMAL payload the resolver needs — enough to prove
|
|
88
|
+
// the framework handles the op, not to produce semantically correct output.
|
|
89
|
+
// Keep entries scoped to fields the resolver actually destructures.
|
|
90
|
+
const OP_PROBE_PAYLOADS = {
|
|
91
|
+
// i2c.write_bytes requires bus + bytes (resolver iterates bytes).
|
|
92
|
+
'i2c.write_bytes': { bus: 'i2c0', address: 0x50, bytes: [0x00, 0x01] },
|
|
93
|
+
// uart.printf requires format + args (resolver joins args).
|
|
94
|
+
'uart.printf': { port: 'Serial', format: '%d', args: ['x'] },
|
|
95
|
+
// adc.read / adc.read_voltage require pin.
|
|
96
|
+
'adc.read': { pin: 0 },
|
|
97
|
+
'adc.read_voltage': { pin: 0 },
|
|
98
|
+
// dac.write requires pin + value. Use pin 25 (a valid DAC pin on ESP32;
|
|
99
|
+
// pin 0 throws "not a DAC pin" on ESP32 and Arduino maps it to a
|
|
100
|
+
// non-DAC pin too).
|
|
101
|
+
'dac.write': { pin: 25, value: 128 },
|
|
102
|
+
// display.* require coordinates/color (still inconclusive without driver
|
|
103
|
+
// context — included so future driver-aware probes can build on them).
|
|
104
|
+
'display.fill_rect': { x: 0, y: 0, w: 10, h: 10, color: 0xffff },
|
|
105
|
+
'display.draw_text': { x: 0, y: 0, text: 'x', font: '8x16', color: 0xffff },
|
|
106
|
+
'display.draw_rect': { x: 0, y: 0, w: 10, h: 10, color: 0xffff },
|
|
107
|
+
'display.flush': { rects: [{ x: 0, y: 0, w: 10, h: 10 }] },
|
|
108
|
+
// fs.* require a path (and content for write_text).
|
|
109
|
+
'fs.read_text': { path: '/sdcard/x.txt' },
|
|
110
|
+
'fs.write_text': { path: '/sdcard/x.txt', content: 'hi' },
|
|
111
|
+
'fs.exists': { path: '/sdcard/x.txt' },
|
|
112
|
+
'fs.remove': { path: '/sdcard/x.txt' },
|
|
113
|
+
// random.range requires min/max.
|
|
114
|
+
'random.range': { min: 0, max: 10 },
|
|
115
|
+
// mdns.add_service requires instance/proto/port.
|
|
116
|
+
'mdns.start': { hostname: 'h' },
|
|
117
|
+
'mdns.set_hostname': { name: 'h' },
|
|
118
|
+
'mdns.add_service': { instance: 'i', proto: '_tcp', port: 80 },
|
|
119
|
+
// mqtt.connect requires brokerUri/clientId; mqtt.publish requires topic/data.
|
|
120
|
+
'mqtt.connect': { brokerUri: 'mqtt://b', clientId: 'c' },
|
|
121
|
+
'mqtt.on_message': { handler: 'cb' },
|
|
122
|
+
'mqtt.subscribe': { topic: 't' },
|
|
123
|
+
'mqtt.publish': { topic: 't', data: 'd' },
|
|
124
|
+
// wifi.on_event requires an event + handler. Probe with 'connect'.
|
|
125
|
+
// Without a payload the probe sends event=undefined and a 'supported'
|
|
126
|
+
// declaration would false-fail as "resolver returned undefined".
|
|
127
|
+
'wifi.on_event': { event: 'connect', handler: 'cb' },
|
|
128
|
+
// ota.from_url requires url; ota.write requires chunk.
|
|
129
|
+
'ota.from_url': { url: 'https://x' },
|
|
130
|
+
'ota.write': { chunk: 'buf' },
|
|
131
|
+
// hwtimer.* require an instance (and hz/handler).
|
|
132
|
+
'hwtimer.set_frequency': { instance: 0, hz: 1000 },
|
|
133
|
+
'hwtimer.on_overflow': { instance: 0, handler: 'cb' },
|
|
134
|
+
'hwtimer.start': { instance: 0 },
|
|
135
|
+
'hwtimer.stop': { instance: 0 },
|
|
136
|
+
// capacitive.read requires pin.
|
|
137
|
+
'capacitive.read': { pin: 4 },
|
|
138
|
+
};
|
|
139
|
+
// Builds a HALOpIR probe. Uses OP_PROBE_PAYLOADS when available so resolvers
|
|
140
|
+
// that destructure required fields don't throw on the probe itself.
|
|
141
|
+
function buildHalProbe(opKind) {
|
|
142
|
+
const payload = OP_PROBE_PAYLOADS[opKind];
|
|
143
|
+
return { operation: opKind, ...(payload ?? {}) };
|
|
144
|
+
}
|
|
145
|
+
function buildDisplayProbe(opKind) {
|
|
146
|
+
const payload = OP_PROBE_PAYLOADS[opKind];
|
|
147
|
+
return { operation: opKind, ...(payload ?? {}) };
|
|
148
|
+
}
|
|
149
|
+
function probeResolve(strategy, opKind) {
|
|
150
|
+
const isDisplay = opKind.startsWith('display.');
|
|
151
|
+
try {
|
|
152
|
+
const op = isDisplay ? buildDisplayProbe(opKind) : buildHalProbe(opKind);
|
|
153
|
+
const result = isDisplay
|
|
154
|
+
? strategy.resolveDisplayOp?.(op)
|
|
155
|
+
: strategy.resolveHALOperation?.(op);
|
|
156
|
+
if (!result)
|
|
157
|
+
return 'undefined';
|
|
158
|
+
if (result.code !== undefined)
|
|
159
|
+
return 'code';
|
|
160
|
+
if (result.expression !== undefined)
|
|
161
|
+
return 'expression';
|
|
162
|
+
return 'undefined';
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return 'thrown';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function statusFromResolution(res) {
|
|
169
|
+
return res === 'undefined' || res === 'thrown' ? 'no-emit' : 'lowers';
|
|
170
|
+
}
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Category A — identity
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
function validateIdentity(manifest, ctx, acc) {
|
|
175
|
+
const strategyId = ctx.strategy.id;
|
|
176
|
+
const matchesDirect = strategyId === manifest.frameworkId;
|
|
177
|
+
const matchesInherited = manifest.inheritsStrategyId !== undefined &&
|
|
178
|
+
manifest.inheritsStrategyId === strategyId;
|
|
179
|
+
if (!matchesDirect && !matchesInherited) {
|
|
180
|
+
acc.error('identity/id-mismatch', 'frameworkId', `manifest.frameworkId is "${manifest.frameworkId}" but strategy.id is "${strategyId}". ` +
|
|
181
|
+
`Set strategy.id to match, or add "inheritsStrategyId: "${strategyId}"" to the manifest ` +
|
|
182
|
+
`to document intentional id reuse.`, manifest.frameworkId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Category B — entrypoint
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
function validateEntrypoint(manifest, ctx, acc) {
|
|
189
|
+
const ep = manifest.entrypoint;
|
|
190
|
+
const strat = ctx.strategy;
|
|
191
|
+
const checks = [
|
|
192
|
+
['entrypointFunctionName', ep.entrypointFunctionName, strat.entrypointFunctionName()],
|
|
193
|
+
['requiresLoopFunction', ep.requiresLoopFunction, strat.requiresLoopFunction()],
|
|
194
|
+
['sourceExtension', ep.sourceExtension, strat.sourceExtension(true, false)],
|
|
195
|
+
['generateHeaderFile', ep.generateHeaderFile, strat.generateHeaderFile()],
|
|
196
|
+
];
|
|
197
|
+
for (const [field, declared, actual] of checks) {
|
|
198
|
+
if (declared !== actual) {
|
|
199
|
+
acc.error(`entrypoint/${field}/mismatch`, `entrypoint.${field}`, `manifest declares ${field}=${JSON.stringify(declared)} but strategy returns ${JSON.stringify(actual)}.`, field);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Category C — HAL coverage
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
function validateHalCoverage(manifest, ctx, acc) {
|
|
207
|
+
for (const [category, declarationRaw] of Object.entries(manifest.hal)) {
|
|
208
|
+
if (category === 'raw')
|
|
209
|
+
continue;
|
|
210
|
+
const declaration = declarationRaw;
|
|
211
|
+
const knownKinds = opKindsForCategory(category);
|
|
212
|
+
// Check that every known op kind for this category appears in ops.
|
|
213
|
+
for (const kind of knownKinds) {
|
|
214
|
+
if (!(kind in declaration.ops)) {
|
|
215
|
+
acc.error(`hal/${category}/op/${kind}/undeclared`, `hal.${category}.ops.${kind}`, `manifest.hal.${category}.ops is missing "${kind}". Add it with a status of supported/stub/unsupported.`, kind);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Probe each declared op and cross-check status vs resolver behavior.
|
|
219
|
+
for (const [kind, status] of Object.entries(declaration.ops)) {
|
|
220
|
+
// 'probe-inconclusive' is an explicit acknowledgment that the minimal
|
|
221
|
+
// probe (just an operation discriminator) cannot determine support —
|
|
222
|
+
// typically because the resolver needs a valid pin/config payload.
|
|
223
|
+
// The validator does not cross-check these; the renderer flags them
|
|
224
|
+
// for manual review. This keeps the matrix honest without penalizing
|
|
225
|
+
// frameworks for the probe's limitations.
|
|
226
|
+
if (status === 'probe-inconclusive')
|
|
227
|
+
continue;
|
|
228
|
+
// 'polyfill' means the op is backed by a runtime polyfill, NOT by
|
|
229
|
+
// resolveHALOperation. Verify the named polyfill exists in
|
|
230
|
+
// POLYFILL_BACKED_OPS and is declared in polyfills.emitted.
|
|
231
|
+
// No HAL probe — the resolver legitimately returns undefined for these.
|
|
232
|
+
if (status === 'polyfill') {
|
|
233
|
+
const expectedPolyfill = POLYFILL_BACKED_OPS[kind];
|
|
234
|
+
if (!expectedPolyfill) {
|
|
235
|
+
acc.error(`hal/${category}/op/${kind}/polyfill-not-recognized`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "polyfill" but is not in POLYFILL_BACKED_OPS. Either add it to packages/cuttlefish/src/api/shared/framework-manifest.ts or use a different status.`, kind);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const emitted = manifest.polyfills.emitted.map((p) => p.id);
|
|
239
|
+
if (!emitted.includes(expectedPolyfill)) {
|
|
240
|
+
acc.error(`hal/${category}/op/${kind}/polyfill-not-declared`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "polyfill" (backed by "${expectedPolyfill}") but "${expectedPolyfill}" is not in polyfills.emitted. Add it, or remove this op from the polyfill status.`, kind);
|
|
241
|
+
}
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const res = probeResolve(ctx.strategy, kind);
|
|
245
|
+
const lowers = statusFromResolution(res) === 'lowers';
|
|
246
|
+
if (status === 'supported' && !lowers) {
|
|
247
|
+
acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "supported" but resolver returned ${res}. Either implement the lowering or change the op status.`, kind);
|
|
248
|
+
}
|
|
249
|
+
else if (status === 'stub' && !lowers) {
|
|
250
|
+
acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "stub" but resolver emitted nothing (${res}). Either emit something or change to "unsupported".`, kind);
|
|
251
|
+
}
|
|
252
|
+
else if (status === 'unsupported' && lowers) {
|
|
253
|
+
acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "unsupported" but resolver actually lowers (${res}). Either mark it supported or remove the lowering.`, kind);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Category-level summary check.
|
|
257
|
+
if (declaration.supported) {
|
|
258
|
+
// If every op is probe-inconclusive, we can't verify the supported
|
|
259
|
+
// claim at all — skip rather than false-positive.
|
|
260
|
+
const hasVerifiable = knownKinds.some((k) => declaration.ops[k] !== 'probe-inconclusive');
|
|
261
|
+
if (hasVerifiable) {
|
|
262
|
+
const anyLowersOrInconclusive = knownKinds.some((k) => {
|
|
263
|
+
if (declaration.ops[k] === 'probe-inconclusive')
|
|
264
|
+
return true;
|
|
265
|
+
if (declaration.ops[k] === 'polyfill')
|
|
266
|
+
return true;
|
|
267
|
+
return statusFromResolution(probeResolve(ctx.strategy, k)) === 'lowers';
|
|
268
|
+
});
|
|
269
|
+
if (!anyLowersOrInconclusive) {
|
|
270
|
+
acc.error(`hal/${category}/declared-supported-but-undefined`, `hal.${category}`, `manifest.hal.${category}.supported is true but resolver returns undefined for every verifiable op kind. Change supported to false with unsupportedReason, or implement the lowering.`, category);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// supported: false. Ignore probe-inconclusive and polyfill ops when
|
|
276
|
+
// checking "does it actually lower?" — polyfill ops legitimately
|
|
277
|
+
// return undefined from the HAL resolver.
|
|
278
|
+
const anyLowers = knownKinds.some((k) => {
|
|
279
|
+
if (declaration.ops[k] === 'probe-inconclusive')
|
|
280
|
+
return false;
|
|
281
|
+
if (declaration.ops[k] === 'polyfill')
|
|
282
|
+
return false;
|
|
283
|
+
return statusFromResolution(probeResolve(ctx.strategy, k)) === 'lowers';
|
|
284
|
+
});
|
|
285
|
+
if (anyLowers) {
|
|
286
|
+
acc.error(`hal/${category}/declared-unsupported-but-actually-lowers`, `hal.${category}`, `manifest.hal.${category}.supported is false but resolver lowers at least one verifiable op. Either mark supported: true or override the resolver to throw/return undefined.`, category);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Category C2 — HAL completeness
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
/**
|
|
295
|
+
* Verify every known HAL category appears in manifest.hal. validateHalCoverage
|
|
296
|
+
* only audits categories the manifest declares; a category omitted entirely is
|
|
297
|
+
* invisible to it (and to the per-op check). This step closes that hole: each
|
|
298
|
+
* category in CATEGORY_PREFIXES must be accounted for, even as unsupported.
|
|
299
|
+
* `raw` is excluded — it is the schema-level escape hatch, not a real category,
|
|
300
|
+
* and is not in CATEGORY_PREFIXES.
|
|
301
|
+
*/
|
|
302
|
+
function validateHalCompleteness(manifest, _ctx, acc) {
|
|
303
|
+
const declared = new Set(Object.keys(manifest.hal));
|
|
304
|
+
for (const category of Object.keys(CATEGORY_PREFIXES)) {
|
|
305
|
+
if (!declared.has(category)) {
|
|
306
|
+
acc.error(`hal/${category}/category-undeclared`, `manifest.hal.${category}`, `manifest.hal is missing the "${category}" category. Add a block declaring it (supported: true/false with ops, or unsupported with unsupportedReason).`, category);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Category D — polyfills
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
function validatePolyfills(manifest, ctx, acc) {
|
|
314
|
+
const strat = ctx.strategy;
|
|
315
|
+
// A polyfill is "produced" if either generateNativePolyfills OR
|
|
316
|
+
// nativePolyfills mentions it. generateNativePolyfills filters by program
|
|
317
|
+
// analysis (may omit polyfills not needed for the empty probe program);
|
|
318
|
+
// nativePolyfills is the unconditional set the strategy handles natively.
|
|
319
|
+
// Unioning both gives the true picture of what the strategy claims to emit.
|
|
320
|
+
const produced = new Set();
|
|
321
|
+
try {
|
|
322
|
+
const irs = strat.generateNativePolyfills?.({ kind: 'program', modules: [], classes: [], functions: [] }, undefined) ?? [];
|
|
323
|
+
for (const ir of irs)
|
|
324
|
+
produced.add(ir.id);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
// generateNativePolyfills may throw on synthetic input; that's fine.
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
const ids = strat.nativePolyfills?.() ?? new Set();
|
|
331
|
+
for (const id of ids)
|
|
332
|
+
produced.add(id);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// nativePolyfills is optional.
|
|
336
|
+
}
|
|
337
|
+
for (const declared of manifest.polyfills.emitted) {
|
|
338
|
+
if (!produced.has(declared.id)) {
|
|
339
|
+
acc.error(`polyfill/${declared.id}/declared-but-not-emitted`, `polyfills.emitted.${declared.id}`, `polyfill "${declared.id}" declared emitted but generateNativePolyfills/nativePolyfills did not produce it.`, declared.id);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (const suppressed of manifest.polyfills.suppressed) {
|
|
343
|
+
if (produced.has(suppressed.id)) {
|
|
344
|
+
acc.error(`polyfill/${suppressed.id}/declared-suppressed-but-emitted`, `polyfills.suppressed.${suppressed.id}`, `polyfill "${suppressed.id}" declared suppressed but the strategy actually emits it.`, suppressed.id);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
// Category E — toolchain
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
function validateToolchain(manifest, ctx, acc) {
|
|
352
|
+
const ops = manifest.toolchain.operations;
|
|
353
|
+
if (!ops.compile) {
|
|
354
|
+
acc.error('toolchain/compile/required', 'toolchain.operations.compile', 'toolchain.operations.compile must be true (LoadedFramework contract requires a compile implementation).', 'compile');
|
|
355
|
+
}
|
|
356
|
+
const tc = ctx.toolchain;
|
|
357
|
+
if (tc) {
|
|
358
|
+
const checks = [
|
|
359
|
+
['prepare', ops.prepare],
|
|
360
|
+
['compile', ops.compile],
|
|
361
|
+
['upload', ops.upload],
|
|
362
|
+
['monitor', ops.monitor],
|
|
363
|
+
];
|
|
364
|
+
for (const [op, declared] of checks) {
|
|
365
|
+
if (declared && typeof tc[op] !== 'function') {
|
|
366
|
+
acc.error(`toolchain/${op}/declared-but-missing`, `toolchain.operations.${op}`, `operation "${op}" declared true but is not a function on the loaded Toolchain.`, op);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
// Category F — library resolution
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
function validateLibraryResolution(manifest, ctx, acc) {
|
|
375
|
+
if (!manifest.libraryResolution)
|
|
376
|
+
return;
|
|
377
|
+
const lr = manifest.libraryResolution;
|
|
378
|
+
const checks = [
|
|
379
|
+
['isFrameworkLibraryImport', lr.isFrameworkLibraryImport],
|
|
380
|
+
['getFrameworkLibraryHeaderName', lr.getFrameworkLibraryHeaderName],
|
|
381
|
+
['buildClassNameMap', lr.buildClassNameMap],
|
|
382
|
+
['tryGenerateLibDecl', lr.tryGenerateLibDecl],
|
|
383
|
+
];
|
|
384
|
+
for (const [name, declared] of checks) {
|
|
385
|
+
if (declared && typeof ctx.moduleExports[name] !== 'function') {
|
|
386
|
+
acc.error(`library-resolution/${name}/declared-but-not-exported`, `libraryResolution.${name}`, `"${name}" declared true but not exported from the framework package index.`, name);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
// Category G — type emission
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
function validateTypeEmission(manifest, ctx, acc) {
|
|
394
|
+
const te = manifest.typeEmission;
|
|
395
|
+
const strat = ctx.strategy;
|
|
396
|
+
const fieldChecks = [
|
|
397
|
+
['mathHeader', te.mathHeader, strat.mathHeader()],
|
|
398
|
+
['needsStdString', te.needsStdString, strat.needsStdString()],
|
|
399
|
+
['needsStdVector', te.needsStdVector, strat.needsStdVector()],
|
|
400
|
+
['needsIostream', te.needsIostream, strat.needsIostream()],
|
|
401
|
+
['needsStdFunction', te.needsStdFunction, strat.needsStdFunction()],
|
|
402
|
+
];
|
|
403
|
+
for (const [field, declared, actual] of fieldChecks) {
|
|
404
|
+
if (declared !== actual) {
|
|
405
|
+
acc.error(`type-emission/${field}/mismatch`, `typeEmission.${field}`, `manifest declares ${field}=${JSON.stringify(declared)} but strategy returns ${JSON.stringify(actual)}.`, field);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const actualStdlib = strat.getStdLibSupport();
|
|
409
|
+
const declaredStdlib = te.stdlibSupport;
|
|
410
|
+
const stdlibEqual = actualStdlib.hasVector === declaredStdlib.hasVector &&
|
|
411
|
+
actualStdlib.hasString === declaredStdlib.hasString &&
|
|
412
|
+
actualStdlib.hasIostream === declaredStdlib.hasIostream &&
|
|
413
|
+
actualStdlib.hasExceptions === declaredStdlib.hasExceptions &&
|
|
414
|
+
actualStdlib.hasRTTI === declaredStdlib.hasRTTI &&
|
|
415
|
+
actualStdlib.recommendedArrayImpl === declaredStdlib.recommendedArrayImpl &&
|
|
416
|
+
actualStdlib.recommendedStringImpl === declaredStdlib.recommendedStringImpl;
|
|
417
|
+
if (!stdlibEqual) {
|
|
418
|
+
acc.error('type-emission/stdlib-support-mismatch', 'typeEmission.stdlibSupport', `manifest stdlibSupport does not match strategy.getStdLibSupport() return.`);
|
|
419
|
+
}
|
|
420
|
+
// Ambient types
|
|
421
|
+
const emittedAmbient = new Set();
|
|
422
|
+
try {
|
|
423
|
+
const decls = strat.ambientTypeDeclarations?.() ?? [];
|
|
424
|
+
for (const d of decls) {
|
|
425
|
+
if (typeof d === 'string') {
|
|
426
|
+
// Match all ambient declarations in the string: interface/type/class NAME
|
|
427
|
+
// and const NAME: (Arduino/AVR/ESP32 emit `const Timing: {...}` etc.).
|
|
428
|
+
const matches = [...d.matchAll(/\b(?:interface|type|class)\s+([A-Za-z_$][\w$]*)/g)];
|
|
429
|
+
for (const m of matches)
|
|
430
|
+
emittedAmbient.add(m[1]);
|
|
431
|
+
const constMatches = [...d.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*:/g)];
|
|
432
|
+
for (const m of constMatches)
|
|
433
|
+
emittedAmbient.add(m[1]);
|
|
434
|
+
}
|
|
435
|
+
else if (d && typeof d === 'object' && 'name' in d) {
|
|
436
|
+
emittedAmbient.add(String(d.name));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
// ambientTypeDeclarations is optional and may require a context.
|
|
442
|
+
}
|
|
443
|
+
for (const declared of manifest.ambientTypes) {
|
|
444
|
+
if (!emittedAmbient.has(declared)) {
|
|
445
|
+
acc.error(`ambient-types/${declared}/declared-but-not-emitted`, `ambientTypes.${declared}`, `ambient type "${declared}" declared but not found in ambientTypeDeclarations() output.`, declared);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (const emitted of emittedAmbient) {
|
|
449
|
+
if (!manifest.ambientTypes.includes(emitted)) {
|
|
450
|
+
acc.warning(`ambient-types/${emitted}/emitted-but-undeclared`, 'ambientTypes', `ambient type "${emitted}" emitted by strategy but not listed in manifest. Consider adding it.`, emitted);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
// Category H — conformance
|
|
456
|
+
// ---------------------------------------------------------------------------
|
|
457
|
+
function validateConformance(manifest, ctx, acc) {
|
|
458
|
+
for (const group of manifest.conformance.hardwareTestGroups) {
|
|
459
|
+
const filePath = path.join(ctx.packageRoot, 'tests', `${group}.test.ts`);
|
|
460
|
+
if (!fs.existsSync(filePath)) {
|
|
461
|
+
acc.error(`conformance/hardware/${group}/file-not-found`, `conformance.hardwareTestGroups.${group}`, `hardware test group "${group}" listed but ${filePath} does not exist.`, group);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const name of manifest.conformance.halResolutionTests) {
|
|
465
|
+
// Test dir convention is tests/packages/<package-dir-name>/hal-resolution/.
|
|
466
|
+
// Package dir name is the last segment of packageName (e.g.
|
|
467
|
+
// "@typecad/framework-esp32" -> "framework-esp32"), not frameworkId.
|
|
468
|
+
const packageDirName = manifest.packageName.split('/').pop() ?? manifest.frameworkId;
|
|
469
|
+
const filePath = path.join(ctx.repoTestsDir, 'packages', packageDirName, 'hal-resolution', `${name}.test.ts`);
|
|
470
|
+
if (!fs.existsSync(filePath)) {
|
|
471
|
+
acc.error(`conformance/hal/${name}/file-not-found`, `conformance.halResolutionTests.${name}`, `HAL resolution test "${name}" listed but ${filePath} does not exist.`, name);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
// Entry point
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
export function validateFrameworkManifest(manifest, ctx) {
|
|
479
|
+
const acc = new Accumulator();
|
|
480
|
+
validateIdentity(manifest, ctx, acc);
|
|
481
|
+
validateEntrypoint(manifest, ctx, acc);
|
|
482
|
+
validateHalCoverage(manifest, ctx, acc);
|
|
483
|
+
validateHalCompleteness(manifest, ctx, acc);
|
|
484
|
+
validatePolyfills(manifest, ctx, acc);
|
|
485
|
+
validateToolchain(manifest, ctx, acc);
|
|
486
|
+
validateLibraryResolution(manifest, ctx, acc);
|
|
487
|
+
validateTypeEmission(manifest, ctx, acc);
|
|
488
|
+
validateConformance(manifest, ctx, acc);
|
|
489
|
+
return {
|
|
490
|
+
valid: acc.errors.length === 0,
|
|
491
|
+
errors: acc.errors,
|
|
492
|
+
warnings: acc.warnings,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ProgramIR } from "../index.js";
|
|
2
|
+
import type { RuntimePolyfillIR } from "./index.js";
|
|
3
|
+
import type { PlatformStrategy } from "./platform-strategy.js";
|
|
4
|
+
import type { WorkerBacking } from "./worker-runtime.js";
|
|
5
|
+
/**
|
|
6
|
+
* Build a RuntimePolyfillIR for the worker-offload runtime, when the framework
|
|
7
|
+
* supplies worker backing via its PlatformAsyncStrategy hooks
|
|
8
|
+
* (workerSpawnLines / workerSignalDoneExpr / workerIsDoneExpr).
|
|
9
|
+
*
|
|
10
|
+
* The polyfill carries only the slot table + __tc_worker_submit/done contract
|
|
11
|
+
* with the per-framework primitives injected from the strategy hooks. STL-free
|
|
12
|
+
* so it links on minimal-libc targets (Zephyr). Returns null if the strategy
|
|
13
|
+
* supplies no backing (worker offload unsupported there) — in which case
|
|
14
|
+
* worker.* ops resolve to unsupported.
|
|
15
|
+
*
|
|
16
|
+
* Callers gate this on the program actually using worker.* ops
|
|
17
|
+
* (ProgramAnalysisResult.usesWorker); the producer does not re-scan.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildWorkerRuntimePolyfill(_program: ProgramIR, strategy: PlatformStrategy, backing: WorkerBacking, options?: {
|
|
20
|
+
poolSize?: number;
|
|
21
|
+
stackSize?: number;
|
|
22
|
+
}): RuntimePolyfillIR | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { generateWorkerRuntime } from "./worker-runtime.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a RuntimePolyfillIR for the worker-offload runtime, when the framework
|
|
4
|
+
* supplies worker backing via its PlatformAsyncStrategy hooks
|
|
5
|
+
* (workerSpawnLines / workerSignalDoneExpr / workerIsDoneExpr).
|
|
6
|
+
*
|
|
7
|
+
* The polyfill carries only the slot table + __tc_worker_submit/done contract
|
|
8
|
+
* with the per-framework primitives injected from the strategy hooks. STL-free
|
|
9
|
+
* so it links on minimal-libc targets (Zephyr). Returns null if the strategy
|
|
10
|
+
* supplies no backing (worker offload unsupported there) — in which case
|
|
11
|
+
* worker.* ops resolve to unsupported.
|
|
12
|
+
*
|
|
13
|
+
* Callers gate this on the program actually using worker.* ops
|
|
14
|
+
* (ProgramAnalysisResult.usesWorker); the producer does not re-scan.
|
|
15
|
+
*/
|
|
16
|
+
export function buildWorkerRuntimePolyfill(_program, strategy, backing, options = {}) {
|
|
17
|
+
// The backing is authoritative: it is constructed by the framework from its
|
|
18
|
+
// own hooks. If a framework supplies no backing (worker offload unsupported),
|
|
19
|
+
// it should not call this producer.
|
|
20
|
+
const runtime = generateWorkerRuntime(backing, options);
|
|
21
|
+
if (!runtime)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
kind: "polyfill",
|
|
25
|
+
id: "worker_runtime",
|
|
26
|
+
domain: "embedded",
|
|
27
|
+
requiredIncludes: backing.requiredIncludes,
|
|
28
|
+
forwardDeclarations: backing.declarations ?? [],
|
|
29
|
+
helperStructs: [runtime],
|
|
30
|
+
helperFunctions: [],
|
|
31
|
+
shimMacros: [],
|
|
32
|
+
dependencies: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface WorkerRuntimeOptions {
|
|
2
|
+
/** Number of fixed worker slots in the pool. Default 4. */
|
|
3
|
+
poolSize?: number;
|
|
4
|
+
/** Per-slot worker stack size in bytes (ESP32). Default 8192. */
|
|
5
|
+
stackSize?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Per-framework backing primitives injected into the shared contract.
|
|
9
|
+
* Undefined fields mean "unsupported" and the runtime is not emitted.
|
|
10
|
+
*/
|
|
11
|
+
export interface WorkerBacking {
|
|
12
|
+
/**
|
|
13
|
+
* Statements that spawn the worker trampoline for slot `handleId`.
|
|
14
|
+
*
|
|
15
|
+
* `trampolineName` is the C++ symbol of the per-slot worker function the
|
|
16
|
+
* shared runtime generated (it reads fn/arg from the slot, runs them, then
|
|
17
|
+
* signals completion). `waiterExpr` is the C++ lvalue the backing should set
|
|
18
|
+
* to the current waiter handle before spawning (the shared runtime reads it
|
|
19
|
+
* via signalDoneExpr/isDoneExpr).
|
|
20
|
+
*
|
|
21
|
+
* The shared runtime has already stored the submitted fn/arg into the slot
|
|
22
|
+
* and cleared `done` before this runs; the backing need only record the
|
|
23
|
+
* waiter and spawn `trampolineName`.
|
|
24
|
+
*/
|
|
25
|
+
spawnLines: (handleId: number, trampolineName: string, waiterExpr: string) => string[];
|
|
26
|
+
/** Expression the worker calls after finishing + writing outputs (barrier). */
|
|
27
|
+
signalDoneExpr: (handleId: number) => string;
|
|
28
|
+
/** Boolean expression polled by __tc_worker_done (observes the barrier). */
|
|
29
|
+
isDoneExpr: (handleId: number) => string;
|
|
30
|
+
/** C++ headers required by the primitives (e.g. '<freertos/FreeRTOS.h>'). */
|
|
31
|
+
requiredIncludes: string[];
|
|
32
|
+
/** Any backing declarations (e.g. semaphore objects) emitted before the pool. */
|
|
33
|
+
declarations?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Per-slot backing declarations emitted inside the namespace, one call per
|
|
36
|
+
* slot, before the submit/done functions. Used by backings that need a named
|
|
37
|
+
* per-slot object bound to the trampoline at definition time — e.g. Zephyr's
|
|
38
|
+
* `K_WORK_DEFINE(_work_<i>, __tc_worker_fn_<i>)` + a per-slot semaphore.
|
|
39
|
+
* `trampolineName` is the per-slot worker symbol the shared runtime emitted.
|
|
40
|
+
*/
|
|
41
|
+
slotDeclarations?: (handleId: number, trampolineName: string) => string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate the worker-offload runtime C++ for a framework that supplies a
|
|
45
|
+
* backing. Returns null if `backing` is null (unsupported on this framework).
|
|
46
|
+
*
|
|
47
|
+
* The emitted code defines, for each slot i in [0, poolSize):
|
|
48
|
+
* - a worker trampoline __tc_worker_fn_i(void* arg) that runs the recorded
|
|
49
|
+
* fn and then signals completion via signalDoneExpr,
|
|
50
|
+
* - __tc_worker_submit(i, fn, arg) — records the waiter, clears done, spawns,
|
|
51
|
+
* - __tc_worker_done(i) — poll predicate.
|
|
52
|
+
*
|
|
53
|
+
* AUTOSAR C++14: fixed-width types, static_cast, final structs, no STL, no heap.
|
|
54
|
+
*/
|
|
55
|
+
export declare function generateWorkerRuntime(backing: WorkerBacking | null, options?: WorkerRuntimeOptions): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a HAL worker.* op to C++ against the shared contract.
|
|
58
|
+
* Returns undefined for ops the caller did not lower (unsupported).
|
|
59
|
+
*
|
|
60
|
+
* Note: the worker function itself (fnRef) and its argument (argRef) come from
|
|
61
|
+
* the IR as flat-scalar strings (per the hal-op-ir flat-fields contract). The
|
|
62
|
+
* lowering emits a straight call into __tc_worker_submit / __tc_worker_done.
|
|
63
|
+
*/
|
|
64
|
+
export declare function lowerWorkerOp(op: {
|
|
65
|
+
operation: string;
|
|
66
|
+
}): {
|
|
67
|
+
code?: string;
|
|
68
|
+
expression?: string;
|
|
69
|
+
} | undefined;
|