@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.8
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/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 +2 -2
- package/dist/api/shared/display-adapter.js +9 -119
- package/dist/api/shared/display-adapters/sdl.js +29 -29
- package/dist/api/shared/display-profile.d.ts +38 -33
- package/dist/api/shared/display-profile.js +22 -170
- package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
- package/dist/api/shared/framework-manifest-registry.js +8 -6
- package/dist/api/shared/framework-manifest.d.ts +147 -28
- package/dist/api/shared/framework-manifest.js +35 -2
- package/dist/api/shared/graphics-strategy.d.ts +15 -6
- package/dist/api/shared/hal-op-ir.d.ts +340 -3
- package/dist/api/shared/hal-op-ir.js +34 -0
- package/dist/api/shared/index.d.ts +9 -1
- package/dist/api/shared/index.js +11 -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/platform-strategy.d.ts +104 -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 +17 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.js +77 -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.js +120 -121
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +20 -19
- package/dist/config-schema.d.ts +107 -65
- 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 +74 -77
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +7 -3
- package/dist/create/init-scaffold.js +0 -62
- package/dist/create/init-templates.js +62 -16
- package/dist/create/init-wizard.js +32 -27
- 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/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +23 -1
- package/dist/emit/emitters/function-emitter-impl.js +96 -6
- 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.js +80 -1
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +217 -36
- package/dist/emit/emitters/top-level-prep.js +6 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +17 -8
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +81 -5
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +7 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +21 -5
- package/dist/emit/utils/async-state-machine.js +39 -12
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/ir/adc-range-validation.js +14 -1
- 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 +4 -4
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +150 -0
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.js +10 -6
- package/dist/ir/hal/hal-parser.js +16 -9
- package/dist/ir/hal/hal-plugins.js +164 -3
- package/dist/ir/identifier-collector.js +21 -4
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/ownership-analysis.js +6 -0
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +42 -0
- package/dist/ir/program-analysis.js +243 -6
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.js +11 -2
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +4 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +51 -0
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- 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 +6 -1
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/orchestrator/graph-builder.js +8 -7
- 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/stores/display-profile-store.d.ts +0 -1
- package/dist/stores/display-profile-store.js +0 -1
- package/dist/testing.d.ts +4 -0
- package/dist/testing.js +5 -0
- package/dist/transpile.js +124 -35
- package/dist/types.d.ts +13 -2
- package/dist/ui-hook.d.ts +0 -2
- package/dist/utils/cli.js +51 -3
- package/package.json +20 -5
- 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/libdef/idf-discovery.d.ts +0 -7
- package/dist/libdef/idf-discovery.js +0 -59
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -11,6 +11,7 @@ import { isKnownHALClass, getCtorIncludes, getHALCtorFieldMap, registerFloatVari
|
|
|
11
11
|
import { httpFactoryVerb, httpUrlArgText } from "../hal/hal-parser.js";
|
|
12
12
|
import { resolveHALCallForVarInit } from "./hal-call-resolver.js";
|
|
13
13
|
import { recordSignal } from "./ui-call-resolver.js";
|
|
14
|
+
import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
|
|
14
15
|
function replaceHalReadBufferPlaceholder(op, varName) {
|
|
15
16
|
if (op.operation === "i2c.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
|
|
16
17
|
return { ...op, buffer: varName };
|
|
@@ -375,6 +376,56 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
375
376
|
localVariableTypes.set(varName, cppType);
|
|
376
377
|
continue;
|
|
377
378
|
}
|
|
379
|
+
// ── safe.<method>(...) — @typecad/safety call as initializer.
|
|
380
|
+
// Handles expression-position safe.* calls like `const r = safe.read(pin)`.
|
|
381
|
+
// The statement-position form (safe.pinMode(...)) is handled in the
|
|
382
|
+
// call-statement transformer. Both produce hal-op IR nodes that
|
|
383
|
+
// routeHALOp() later dispatches to the safety hook.
|
|
384
|
+
if (hasSafetyHook() &&
|
|
385
|
+
ts.isCallExpression(declaration.initializer) &&
|
|
386
|
+
ts.isPropertyAccessExpression(declaration.initializer.expression) &&
|
|
387
|
+
ts.isIdentifier(declaration.initializer.expression.expression) &&
|
|
388
|
+
declaration.initializer.expression.expression.text === "safe") {
|
|
389
|
+
const method = declaration.initializer.expression.name.text;
|
|
390
|
+
const argValues = declaration.initializer.arguments.map((a) => {
|
|
391
|
+
if (ts.isNumericLiteral(a))
|
|
392
|
+
return Number(a.text);
|
|
393
|
+
if (ts.isStringLiteral(a))
|
|
394
|
+
return a.text;
|
|
395
|
+
if (a.kind === ts.SyntaxKind.TrueKeyword)
|
|
396
|
+
return true;
|
|
397
|
+
if (a.kind === ts.SyntaxKind.FalseKeyword)
|
|
398
|
+
return false;
|
|
399
|
+
if (ts.isIdentifier(a)) {
|
|
400
|
+
const name = a.text;
|
|
401
|
+
if (name === "INPUT")
|
|
402
|
+
return 0;
|
|
403
|
+
if (name === "OUTPUT")
|
|
404
|
+
return 1;
|
|
405
|
+
if (name === "INPUT_PULLUP")
|
|
406
|
+
return 2;
|
|
407
|
+
return name;
|
|
408
|
+
}
|
|
409
|
+
return undefined;
|
|
410
|
+
});
|
|
411
|
+
const op = requireSafetyHook().resolveSemanticCall?.(`safe.${method}`, argValues);
|
|
412
|
+
if (op) {
|
|
413
|
+
// Emit a var_decl initialized by a hal-expr carrying the safety op.
|
|
414
|
+
// routeHALOp() resolves it to the C++ expression at emit time.
|
|
415
|
+
const initExpr = { kind: "hal-expr", operation: op };
|
|
416
|
+
lowered.push({
|
|
417
|
+
kind: "var_decl",
|
|
418
|
+
name: varName,
|
|
419
|
+
storage,
|
|
420
|
+
cppType: "auto",
|
|
421
|
+
initializer: initExpr,
|
|
422
|
+
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
423
|
+
});
|
|
424
|
+
// auto-deduced to SafeReadResult on the C++ side.
|
|
425
|
+
localVariableTypes.set(varName, "auto");
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
378
429
|
if (ts.isNewExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression)) {
|
|
379
430
|
const className = declaration.initializer.expression.text;
|
|
380
431
|
const ctorArgs = declaration.initializer.arguments;
|
|
@@ -48,6 +48,14 @@ const BOARD_CONSTANT_TYPE_NAMES = new Set([
|
|
|
48
48
|
const OWNERSHIP_WRAPPER_TYPE_NAMES = new Set([
|
|
49
49
|
"Owned", "Shared", "Mutable",
|
|
50
50
|
]);
|
|
51
|
+
/** Safety wrapper types: like ownership wrappers (phantom TS types that carry
|
|
52
|
+
* a type parameter), but instead of stripping the wrapper, the C++ keeps it
|
|
53
|
+
* as a template instantiation: SafeVariable<number> → SafeVariable<int32_t>.
|
|
54
|
+
* The C++ template definition is provided by the safety polyfill. */
|
|
55
|
+
const SAFE_WRAPPER_TYPE_NAMES = new Set([
|
|
56
|
+
"SafeVariable",
|
|
57
|
+
"SafeInt",
|
|
58
|
+
]);
|
|
51
59
|
export function resolveAliasedTypeNode(node, typeAliases, visited = new Set()) {
|
|
52
60
|
if (!node || !typeAliases) {
|
|
53
61
|
return node;
|
|
@@ -147,6 +155,16 @@ export function typeNodeToCppType(node, typeAliases, typeParametersInScope) {
|
|
|
147
155
|
const innerTypeNode = node.typeArguments?.[0];
|
|
148
156
|
return typeNodeToCppType(innerTypeNode, typeAliases, typeParametersInScope);
|
|
149
157
|
}
|
|
158
|
+
// SafeVariable<T> is a safety wrapper: unlike ownership wrappers (which
|
|
159
|
+
// strip the wrapper name entirely), SafeVariable keeps the wrapper name
|
|
160
|
+
// in the C++ type because the polyfill provides a template definition.
|
|
161
|
+
// SafeVariable<number> → SafeVariable<int> (the template is emitted by
|
|
162
|
+
// the safety polyfill's helperStructs).
|
|
163
|
+
if (SAFE_WRAPPER_TYPE_NAMES.has(wrapperName)) {
|
|
164
|
+
const innerTypeNode = node.typeArguments?.[0];
|
|
165
|
+
const innerCppType = typeNodeToCppType(innerTypeNode, typeAliases, typeParametersInScope);
|
|
166
|
+
return `${wrapperName}<${innerCppType}>`;
|
|
167
|
+
}
|
|
150
168
|
}
|
|
151
169
|
const resolvedNode = resolveAliasedTypeNode(node, typeAliases) ?? node;
|
|
152
170
|
// If the resolved node is an object literal type (e.g. type X = { a: number }),
|
|
@@ -81,7 +81,7 @@ function autoWireNode(treeName, node, nodeIndex) {
|
|
|
81
81
|
nodeIndex,
|
|
82
82
|
property: "text",
|
|
83
83
|
fnName: `__ui_bindtext_${nodeIndex}`,
|
|
84
|
-
cppBody: `snprintf(buf, size, "%s", ${sig});`,
|
|
84
|
+
cppBody: `snprintf(buf, static_cast<size_t>(size), "%s", ${sig});`,
|
|
85
85
|
});
|
|
86
86
|
// Write: keyboard commit → signal.set(text).
|
|
87
87
|
recordInputBinding({
|
|
@@ -156,13 +156,15 @@ function autoWireNode(treeName, node, nodeIndex) {
|
|
|
156
156
|
fnName: `__ui_${node.id}_autoclick`,
|
|
157
157
|
callbackBody: `__ui_nodes[${nodeIndex}].value = (__ui_nodes[${nodeIndex}].value + 1) % ${count};`,
|
|
158
158
|
});
|
|
159
|
-
// Auto-bind text to show the current option via snprintf if/else chain
|
|
159
|
+
// Auto-bind text to show the current option via snprintf if/else chain.
|
|
160
|
+
// Cast size to size_t to satisfy -Wformat (snprintf's n param is size_t;
|
|
161
|
+
// the textFn signature uses uint8_t).
|
|
160
162
|
const branches = options.map((opt, i) => {
|
|
161
163
|
if (i === 0)
|
|
162
|
-
return `if (__ui_nodes[${nodeIndex}].value == 0) { snprintf(buf, size, "%s", "${opt}"); }`;
|
|
163
|
-
return `else if (__ui_nodes[${nodeIndex}].value == ${i}) { snprintf(buf, size, "%s", "${opt}"); }`;
|
|
164
|
+
return `if (__ui_nodes[${nodeIndex}].value == 0) { snprintf(buf, static_cast<size_t>(size), "%s", "${opt}"); }`;
|
|
165
|
+
return `else if (__ui_nodes[${nodeIndex}].value == ${i}) { snprintf(buf, static_cast<size_t>(size), "%s", "${opt}"); }`;
|
|
164
166
|
}).join(" ");
|
|
165
|
-
const elseBranch = `else { snprintf(buf, size, "%s", "${options[0] || ""}"); }`;
|
|
167
|
+
const elseBranch = `else { snprintf(buf, static_cast<size_t>(size), "%s", "${options[0] || ""}"); }`;
|
|
166
168
|
recordBinding({
|
|
167
169
|
nodeIndex,
|
|
168
170
|
property: "text",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProgramIR, StatementIR } from "../../api/shared/index.js";
|
|
2
|
+
/** Map each statement in the program to zero or more statements. The result
|
|
3
|
+
* is a new ProgramIR; the input is not modified. */
|
|
4
|
+
export declare function mapProgramStatements(program: ProgramIR, fn: (stmt: StatementIR) => StatementIR[]): ProgramIR;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// mapProgramStatements — produce a NEW ProgramIR with statements mapped by fn.
|
|
3
|
+
//
|
|
4
|
+
// The existing walkers in walk-ir.ts are read-only; filterProgramIR
|
|
5
|
+
// (ir/filter.ts) produces a new IR but only filters, never injects. This
|
|
6
|
+
// helper extends the "produce-new-IR" idiom to "map each statement to N
|
|
7
|
+
// statements," which is what the safety pinMode-intercept pass needs to
|
|
8
|
+
// inject companion ops.
|
|
9
|
+
//
|
|
10
|
+
// Non-mutating: every statement container is rebuilt; the input program is
|
|
11
|
+
// untouched. The shape mirrors walkProgramIR's enumeration of containers
|
|
12
|
+
// (walk-ir.ts:107-134): topLevelStatements, functions, classes
|
|
13
|
+
// (constructor/methods/getters/setters), namespaces (recursively).
|
|
14
|
+
//
|
|
15
|
+
// KNOWN LIMITATION (Part A scope): does NOT recurse into nested statement
|
|
16
|
+
// bodies (if/for/while/switch/cases). Statements at the top level of a
|
|
17
|
+
// function body, class method, namespace function, or the file's top level
|
|
18
|
+
// ARE mapped. pinMode calls inside `if (...) { pinMode(...); }` are not. The
|
|
19
|
+
// safety package's design (docs/superpowers/specs/2026-07-27-safety-package-
|
|
20
|
+
// part-a-design.md) explicitly bounds Part A to this. A future iteration can
|
|
21
|
+
// thread mapping into nested bodies by recursing into thenBranch/elseBranch/
|
|
22
|
+
// body/initializer/increment/cases — see walkNestedStatements (walk-ir.ts:3).
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/** Map each statement in the program to zero or more statements. The result
|
|
25
|
+
* is a new ProgramIR; the input is not modified. */
|
|
26
|
+
export function mapProgramStatements(program, fn) {
|
|
27
|
+
const mapList = (stmts) => {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const s of stmts) {
|
|
30
|
+
const mapped = fn(s);
|
|
31
|
+
for (const m of mapped)
|
|
32
|
+
out.push(m);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
};
|
|
36
|
+
const newFunctions = program.functions.map((f) => ({
|
|
37
|
+
...f,
|
|
38
|
+
statements: mapList(f.statements),
|
|
39
|
+
}));
|
|
40
|
+
const newClasses = program.classes.map((cls) => ({
|
|
41
|
+
...cls,
|
|
42
|
+
constructor: cls.constructor
|
|
43
|
+
? { ...cls.constructor, statements: mapList(cls.constructor.statements) }
|
|
44
|
+
: cls.constructor,
|
|
45
|
+
methods: cls.methods.map((m) => ({ ...m, statements: mapList(m.statements) })),
|
|
46
|
+
getters: cls.getters.map((g) => ({ ...g, statements: mapList(g.statements) })),
|
|
47
|
+
setters: cls.setters.map((s) => ({ ...s, statements: mapList(s.statements) })),
|
|
48
|
+
}));
|
|
49
|
+
const newNamespaces = program.namespaces.map((ns) => mapNamespace(ns, fn));
|
|
50
|
+
return {
|
|
51
|
+
...program,
|
|
52
|
+
topLevelStatements: mapList(program.topLevelStatements),
|
|
53
|
+
functions: newFunctions,
|
|
54
|
+
classes: newClasses,
|
|
55
|
+
namespaces: newNamespaces,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function mapNamespace(ns, fn) {
|
|
59
|
+
const mapList = (stmts) => {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const s of stmts)
|
|
62
|
+
out.push(...fn(s));
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
...ns,
|
|
67
|
+
functions: ns.functions.map((f) => ({ ...f, statements: mapList(f.statements) })),
|
|
68
|
+
classes: ns.classes.map((cls) => ({
|
|
69
|
+
...cls,
|
|
70
|
+
constructor: cls.constructor
|
|
71
|
+
? { ...cls.constructor, statements: mapList(cls.constructor.statements) }
|
|
72
|
+
: cls.constructor,
|
|
73
|
+
methods: cls.methods.map((m) => ({ ...m, statements: mapList(m.statements) })),
|
|
74
|
+
getters: cls.getters.map((g) => ({ ...g, statements: mapList(g.statements) })),
|
|
75
|
+
setters: cls.setters.map((s) => ({ ...s, statements: mapList(s.statements) })),
|
|
76
|
+
})),
|
|
77
|
+
children: ns.children ? ns.children.map((c) => mapNamespace(c, fn)) : ns.children,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveStrategy } from "../platform/registry.js";
|
|
2
2
|
import { hasLoadedFramework, getLoadedFramework } from "../framework-registry.js";
|
|
3
3
|
import { analyzeInterruptSafety, inferVolatileForIsrSharedVars, detectReentrancyRisk } from "./interrupt-analysis.js";
|
|
4
|
+
import { analyzeWorkerIsolation } from "./worker-analysis.js";
|
|
4
5
|
import { validateADCRange } from "./adc-range-validation.js";
|
|
5
6
|
import { createEmptyPeripheralUsage } from "./peripheral-usage.js";
|
|
6
7
|
import { validatePeripherals } from "./peripheral-validation.js";
|
|
@@ -31,9 +32,13 @@ export function runProgramValidations(program, strategy) {
|
|
|
31
32
|
diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants, program.fileName));
|
|
32
33
|
diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants, program.fileName));
|
|
33
34
|
diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants, program.fileName));
|
|
34
|
-
diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage));
|
|
35
|
+
diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage, resolvedStrategy.isrUnsafeOperations?.()));
|
|
35
36
|
inferVolatileForIsrSharedVars(program, diagnostics);
|
|
36
37
|
detectReentrancyRisk(program, diagnostics);
|
|
38
|
+
// Worker isolation: bus access in workers is a hard error; worker-shared
|
|
39
|
+
// globals get volatile promotion (info) or a data-race warning. Only fires
|
|
40
|
+
// when worker.submit ops are present, so non-worker programs are unaffected.
|
|
41
|
+
analyzeWorkerIsolation(program, diagnostics);
|
|
37
42
|
diagnostics.push(...validateADCRange(program, program.boardConstants));
|
|
38
43
|
diagnostics.push(...validateUnitSuspicion(program));
|
|
39
44
|
diagnostics.push(...validatePinModeConfig(program));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ProgramIR } from '../api/index.js';
|
|
2
|
+
import type { Diagnostic } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Analyze worker isolation: (a) promote volatile for worker-shared globals,
|
|
5
|
+
* (b) flag shared-mutable globals, (c) hard-error on bus access in workers.
|
|
6
|
+
*
|
|
7
|
+
* Mutates the IR for (a) (sets isVolatile=true on qualifying var_decls) and
|
|
8
|
+
* pushes diagnostics for all three tiers. Returns the diagnostics it added.
|
|
9
|
+
*/
|
|
10
|
+
export declare function analyzeWorkerIsolation(program: ProgramIR, diagnostics: Diagnostic[]): Diagnostic[];
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Worker Isolation Analysis (Phase 1/2)
|
|
3
|
+
//
|
|
4
|
+
// Models the safety property of the generalized worker-offload feature: worker
|
|
5
|
+
// functions run on a separate thread of control (a FreeRTOS task / Zephyr
|
|
6
|
+
// workqueue thread), so any shared mutable state between a worker and the main
|
|
7
|
+
// loop() is a real data race that the cooperative model otherwise eliminates.
|
|
8
|
+
//
|
|
9
|
+
// This pass mirrors interrupt-analysis.ts but splits the verdict by the surface
|
|
10
|
+
// a worker shares with main:
|
|
11
|
+
//
|
|
12
|
+
// (a) Shared GLOBALS — the worker writes a global the main loop reads (or
|
|
13
|
+
// both write it). The worker can run on a different core, so:
|
|
14
|
+
// - worker-written + main-read → promote to volatile + info diagnostic
|
|
15
|
+
// (mirrors the ISR volatile pass; the barrier contract makes the
|
|
16
|
+
// write visible, volatile stops the compiler caching the read).
|
|
17
|
+
// - worker-written + main-written (mutated by both) → warning:
|
|
18
|
+
// a true data race the barrier alone cannot fix.
|
|
19
|
+
//
|
|
20
|
+
// (b) Shared BUSES (I2C/SPI/UART/Wire/Serial) — a worker touching a bus is a
|
|
21
|
+
// HARD ERROR in v1. Rationale: the existing take()/release() bus-ownership
|
|
22
|
+
// mechanism is statically enforced (peripheral-ownership.ts) but runtime-
|
|
23
|
+
// UNIMPLEMENTED (take() is a no-op). A bus is a protocol-level conflict
|
|
24
|
+
// (two masters on the wire), so leaning on a lock that isn't there would
|
|
25
|
+
// be unsound. Workers are compute/pure-only in v1. When the take/release
|
|
26
|
+
// runtime backend lands (the named follow-on), this tier flips from
|
|
27
|
+
// error → allowed-by-take/release.
|
|
28
|
+
//
|
|
29
|
+
// Worker function identification: a worker function is one referenced by a
|
|
30
|
+
// worker.submit HAL op (expr.operation.operation === 'worker.submit'). The op
|
|
31
|
+
// carries fnRef (the C++ symbol), which we correlate to a program function by
|
|
32
|
+
// name. This mirrors how interrupt-analysis identifies ISRs via
|
|
33
|
+
// isInterruptHandler, but resolves through the HAL op instead of a callback
|
|
34
|
+
// flag (worker functions are plain functions, not callbacks).
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
import { collectAssignedNames, collectReadNames } from './interrupt-analysis.js';
|
|
37
|
+
import { walkProgramIR } from './utils/walk-ir.js';
|
|
38
|
+
// Bus-object prefixes a worker is forbidden from touching in v1. Mirrors the
|
|
39
|
+
// getBusName regex in peripheral-ownership.ts (I2C/SPI/UART/Wire/Serial).
|
|
40
|
+
const BUS_PREFIXES = ['I2C', 'SPI', 'UART', 'Wire', 'Serial'];
|
|
41
|
+
/** True if an identifier/callee string names a bus object (forbidden in workers). */
|
|
42
|
+
function isBusName(name) {
|
|
43
|
+
// Wire / Serial exactly, or I2C<n> / SPI<n> / UART<n> / Serial<n>.
|
|
44
|
+
if (name === 'Wire' || name === 'Serial')
|
|
45
|
+
return true;
|
|
46
|
+
for (const p of ['I2C', 'SPI', 'UART']) {
|
|
47
|
+
if (new RegExp(`^${p}\\d`).test(name))
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (/^Serial\d/.test(name))
|
|
51
|
+
return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Walk every expression in the program, invoking the visitor for worker.submit
|
|
56
|
+
* HAL ops. HAL ops appear as expressions whose `.operation.operation` is the
|
|
57
|
+
* op-kind string (mirrors the scan in program-analysis.ts:348).
|
|
58
|
+
*/
|
|
59
|
+
function scanForWorkerSubmitOps(program, visit) {
|
|
60
|
+
walkProgramIR(program, (stmt) => {
|
|
61
|
+
walkExpressionsInStatementLocal(stmt, (expr) => {
|
|
62
|
+
if (expr && typeof expr === 'object'
|
|
63
|
+
&& expr.operation && typeof expr.operation === 'object'
|
|
64
|
+
&& expr.operation.operation === 'worker.submit') {
|
|
65
|
+
const fnRef = expr.operation.fnRef;
|
|
66
|
+
if (typeof fnRef === 'string')
|
|
67
|
+
visit(fnRef, expr.sourceSpan ?? expr.operation.sourceSpan);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Collect the set of bus object names referenced (read/called) in a body. */
|
|
73
|
+
function collectBusAccesses(stmts, hits) {
|
|
74
|
+
for (const stmt of stmts) {
|
|
75
|
+
collectBusAccessesInStatement(stmt, hits);
|
|
76
|
+
const nested = getNestedStatements(stmt);
|
|
77
|
+
if (nested)
|
|
78
|
+
collectBusAccesses(nested, hits);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function collectBusAccessesInStatement(stmt, hits) {
|
|
82
|
+
const s = stmt;
|
|
83
|
+
// Method calls like Wire.begin() / I2C0.writeByte(...): callee is "Wire.begin".
|
|
84
|
+
if (s.callee && typeof s.callee === 'string') {
|
|
85
|
+
const base = s.callee.split('.')[0];
|
|
86
|
+
if (isBusName(base))
|
|
87
|
+
hits.push({ name: base, span: s.sourceSpan });
|
|
88
|
+
}
|
|
89
|
+
// Identifier references (Wire, Serial, I2C0 used bare).
|
|
90
|
+
const visit = (expr) => {
|
|
91
|
+
if (expr && typeof expr === 'object' && expr.kind === 'identifier' && typeof expr.value === 'string') {
|
|
92
|
+
if (isBusName(expr.value))
|
|
93
|
+
hits.push({ name: expr.value, span: expr.sourceSpan });
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
walkExpressionsInStatementLocal(stmt, visit);
|
|
97
|
+
}
|
|
98
|
+
function walkExpressionsInStatementLocal(stmt, visitor) {
|
|
99
|
+
const s = stmt;
|
|
100
|
+
if (s.initializer && typeof s.initializer === 'object')
|
|
101
|
+
walkExpressionsInExpressionLocal(s.initializer, visitor);
|
|
102
|
+
if (s.value && typeof s.value === 'object' && 'kind' in s.value)
|
|
103
|
+
walkExpressionsInExpressionLocal(s.value, visitor);
|
|
104
|
+
if (s.condition && typeof s.condition === 'object')
|
|
105
|
+
walkExpressionsInExpressionLocal(s.condition, visitor);
|
|
106
|
+
if (s.expression && typeof s.expression === 'object' && 'kind' in s.expression)
|
|
107
|
+
walkExpressionsInExpressionLocal(s.expression, visitor);
|
|
108
|
+
if (Array.isArray(s.args))
|
|
109
|
+
for (const a of s.args)
|
|
110
|
+
walkExpressionsInExpressionLocal(a, visitor);
|
|
111
|
+
if (s.callee && typeof s.callee === 'object')
|
|
112
|
+
walkExpressionsInExpressionLocal(s.callee, visitor);
|
|
113
|
+
}
|
|
114
|
+
function walkExpressionsInExpressionLocal(expr, visitor) {
|
|
115
|
+
if (!expr || typeof expr !== 'object')
|
|
116
|
+
return;
|
|
117
|
+
visitor(expr);
|
|
118
|
+
for (const k of ['left', 'right', 'object', 'property', 'value', 'condition', 'whenTrue', 'whenFalse', 'expression', 'callee', 'target']) {
|
|
119
|
+
const v = expr[k];
|
|
120
|
+
if (v && typeof v === 'object' && 'kind' in v)
|
|
121
|
+
walkExpressionsInExpressionLocal(v, visitor);
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(expr.args))
|
|
124
|
+
for (const a of expr.args)
|
|
125
|
+
walkExpressionsInExpressionLocal(a, visitor);
|
|
126
|
+
if (Array.isArray(expr.elements))
|
|
127
|
+
for (const a of expr.elements)
|
|
128
|
+
walkExpressionsInExpressionLocal(a, visitor);
|
|
129
|
+
}
|
|
130
|
+
function getNestedStatements(stmt) {
|
|
131
|
+
// Reuse the walk-ir helper if it exposes nesting; otherwise return undefined.
|
|
132
|
+
const s = stmt;
|
|
133
|
+
if (Array.isArray(s.body))
|
|
134
|
+
return s.body;
|
|
135
|
+
if (Array.isArray(s.statements))
|
|
136
|
+
return s.statements;
|
|
137
|
+
if (s.thenBlock && Array.isArray(s.thenBlock.statements))
|
|
138
|
+
return s.thenBlock.statements;
|
|
139
|
+
if (s.elseBlock && Array.isArray(s.elseBlock.statements))
|
|
140
|
+
return s.elseBlock.statements;
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
/** Find a function in program.functions by name (or originalName). */
|
|
144
|
+
function findFunctionByName(program, name) {
|
|
145
|
+
return program.functions.find((fn) => fn.name === name || fn.originalName === name);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Analyze worker isolation: (a) promote volatile for worker-shared globals,
|
|
149
|
+
* (b) flag shared-mutable globals, (c) hard-error on bus access in workers.
|
|
150
|
+
*
|
|
151
|
+
* Mutates the IR for (a) (sets isVolatile=true on qualifying var_decls) and
|
|
152
|
+
* pushes diagnostics for all three tiers. Returns the diagnostics it added.
|
|
153
|
+
*/
|
|
154
|
+
export function analyzeWorkerIsolation(program, diagnostics) {
|
|
155
|
+
const added = [];
|
|
156
|
+
const push = (d) => { diagnostics.push(d); added.push(d); };
|
|
157
|
+
// 1. Identify worker-submitted functions via worker.submit HAL ops.
|
|
158
|
+
const workerFnNames = new Set();
|
|
159
|
+
scanForWorkerSubmitOps(program, (fnRef) => {
|
|
160
|
+
// fnRef is the C++ symbol; the IR function name is usually the same root.
|
|
161
|
+
workerFnNames.add(fnRef);
|
|
162
|
+
});
|
|
163
|
+
if (workerFnNames.size === 0)
|
|
164
|
+
return added;
|
|
165
|
+
// 2. Collect the bodies of worker functions present in this program.
|
|
166
|
+
const workerBodies = [];
|
|
167
|
+
for (const name of workerFnNames) {
|
|
168
|
+
const fn = findFunctionByName(program, name);
|
|
169
|
+
if (fn && Array.isArray(fn.statements))
|
|
170
|
+
workerBodies.push(fn.statements);
|
|
171
|
+
}
|
|
172
|
+
if (workerBodies.length === 0)
|
|
173
|
+
return added;
|
|
174
|
+
// ── Tier (b): bus access in workers → HARD ERROR ────────────────────────
|
|
175
|
+
for (let i = 0; i < workerFnNames.size; i++) {
|
|
176
|
+
const name = [...workerFnNames][i];
|
|
177
|
+
const fn = findFunctionByName(program, name);
|
|
178
|
+
if (!fn || !Array.isArray(fn.statements))
|
|
179
|
+
continue;
|
|
180
|
+
const busHits = [];
|
|
181
|
+
collectBusAccesses(fn.statements, busHits);
|
|
182
|
+
for (const hit of busHits) {
|
|
183
|
+
push({
|
|
184
|
+
severity: 'error',
|
|
185
|
+
message: `Worker function '${name}' accesses bus '${hit.name}'. Bus access is forbidden in worker functions because the take()/release() bus-ownership lock is not implemented at runtime — two threads would race on the wire despite validation passing.`,
|
|
186
|
+
hint: `Keep workers compute/pure-only. Bus-sharing workers require the take()/release() runtime backend (a planned follow-on).`,
|
|
187
|
+
filePath: (hit.span ?? fn.sourceSpan)?.filePath,
|
|
188
|
+
line: (hit.span ?? fn.sourceSpan)?.startLine,
|
|
189
|
+
column: (hit.span ?? fn.sourceSpan)?.startColumn,
|
|
190
|
+
code: 'worker-bus-access-forbidden',
|
|
191
|
+
source: 'worker-analysis',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// ── Tier (a): shared globals → volatile + warning ───────────────────────
|
|
196
|
+
const globalVars = new Map();
|
|
197
|
+
for (const stmt of program.topLevelStatements) {
|
|
198
|
+
if (stmt.kind === 'var_decl' && typeof stmt.name === 'string') {
|
|
199
|
+
globalVars.set(stmt.name, stmt);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (globalVars.size === 0)
|
|
203
|
+
return added;
|
|
204
|
+
// Names assigned inside worker bodies.
|
|
205
|
+
const workerAssigned = new Set();
|
|
206
|
+
for (const body of workerBodies)
|
|
207
|
+
collectAssignedNames(body, workerAssigned);
|
|
208
|
+
if (workerAssigned.size === 0)
|
|
209
|
+
return added;
|
|
210
|
+
// Names assigned AND read in main (non-worker) code.
|
|
211
|
+
const mainAssigned = new Set();
|
|
212
|
+
const mainRead = new Set();
|
|
213
|
+
for (const stmt of program.topLevelStatements) {
|
|
214
|
+
collectReadNames([stmt], mainRead);
|
|
215
|
+
collectAssignedNames([stmt], mainAssigned);
|
|
216
|
+
}
|
|
217
|
+
for (const fn of program.functions) {
|
|
218
|
+
// Skip the worker functions themselves when collecting main usage.
|
|
219
|
+
if (workerFnNames.has(fn.name) || workerFnNames.has(fn.originalName))
|
|
220
|
+
continue;
|
|
221
|
+
if (Array.isArray(fn.statements)) {
|
|
222
|
+
collectReadNames(fn.statements, mainRead);
|
|
223
|
+
collectAssignedNames(fn.statements, mainAssigned);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const [name, stmt] of globalVars) {
|
|
227
|
+
if (!workerAssigned.has(name))
|
|
228
|
+
continue;
|
|
229
|
+
const workerShared = stmt;
|
|
230
|
+
if (mainRead.has(name) && !mainAssigned.has(name)) {
|
|
231
|
+
// worker-written + main-read → promote to volatile (the barrier makes the
|
|
232
|
+
// write visible; volatile stops the compiler caching the read).
|
|
233
|
+
if (!workerShared.isVolatile) {
|
|
234
|
+
workerShared.isVolatile = true;
|
|
235
|
+
push({
|
|
236
|
+
severity: 'info',
|
|
237
|
+
message: `'${name}' is written in a worker function and read in main code — emitted as \`volatile\` so the compiler does not cache it in a register across the worker/main boundary.`,
|
|
238
|
+
filePath: workerShared.sourceSpan?.filePath,
|
|
239
|
+
line: workerShared.sourceSpan?.startLine,
|
|
240
|
+
column: workerShared.sourceSpan?.startColumn,
|
|
241
|
+
code: 'volatile-worker-shared',
|
|
242
|
+
source: 'worker-analysis',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (mainAssigned.has(name)) {
|
|
247
|
+
// worker-written + main-written → true data race; barrier alone can't fix.
|
|
248
|
+
push({
|
|
249
|
+
severity: 'warning',
|
|
250
|
+
message: `'${name}' is written by both a worker function and main-thread code — a data race the worker completion barrier alone cannot fix. Restructure so only one side writes it (e.g. the worker writes; main only reads).`,
|
|
251
|
+
hint: `Move all writes to '${name}' into the worker (or into main); do not mutate it from both.`,
|
|
252
|
+
filePath: workerShared.sourceSpan?.filePath,
|
|
253
|
+
line: workerShared.sourceSpan?.startLine,
|
|
254
|
+
column: workerShared.sourceSpan?.startColumn,
|
|
255
|
+
code: 'worker-shared-mutable',
|
|
256
|
+
source: 'worker-analysis',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return added;
|
|
261
|
+
}
|
|
@@ -121,7 +121,7 @@ export function collectTranspileGraph(entryFile, boardPackage) {
|
|
|
121
121
|
}
|
|
122
122
|
if (!moduleSpecifier)
|
|
123
123
|
continue;
|
|
124
|
-
if (moduleSpecifier === "@typecad/expect" || moduleSpecifier === "@typecad/ui")
|
|
124
|
+
if (moduleSpecifier === "@typecad/expect" || moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/safety")
|
|
125
125
|
continue;
|
|
126
126
|
if (moduleSpecifier.startsWith("@typecad/"))
|
|
127
127
|
continue;
|
|
@@ -159,12 +159,13 @@ export function collectTranspileGraph(entryFile, boardPackage) {
|
|
|
159
159
|
if (moduleSpecifier === "@typecad/expect") {
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
|
-
// Skip @typecad/ui
|
|
163
|
-
//
|
|
164
|
-
// intercepted
|
|
165
|
-
// must NOT be emitted as
|
|
166
|
-
// _ui_t
|
|
167
|
-
|
|
162
|
+
// Skip @typecad/ui and @typecad/safety — they provide compile-time
|
|
163
|
+
// authoring stubs only. Their calls (ui.mount/signal/bind, safe.read)
|
|
164
|
+
// are intercepted at IR-build time and lowered to IR; the packages
|
|
165
|
+
// themselves must NOT be emitted as C++ modules (they would synthesize
|
|
166
|
+
// bogus _ui_t / _safe_t structs). All runtime definitions the user code
|
|
167
|
+
// needs (enums, structs, shims) are provided by the safety polyfill.
|
|
168
|
+
if (moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/safety") {
|
|
168
169
|
continue;
|
|
169
170
|
}
|
|
170
171
|
// Skip @typecad/board, @typecad/board-*, @typecad/mcu-*, @typecad/hal,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProgramIR } from "../api/index.js";
|
|
2
|
+
import type { RuntimePolyfillIR } from "../api/shared/index.js";
|
|
3
|
+
import type { AsyncRuntimeConfig } from "../api/shared/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Build a RuntimePolyfillIR for the no-STL cooperative scheduler, when the
|
|
6
|
+
* program has async/timer work AND the strategy has opted into priority/time-
|
|
7
|
+
* budget scheduling via AsyncRuntimeConfig.
|
|
8
|
+
*
|
|
9
|
+
* The polyfill carries only the scheduler namespace + CoopSched_run() alias.
|
|
10
|
+
* Per-program work-unit registration + dispatch lines are emitted separately
|
|
11
|
+
* by each strategy's asyncLoopInjection() via buildCoopSchedInjection().
|
|
12
|
+
*
|
|
13
|
+
* Why a polyfill and not shimLines(): polyfills are dead-code-eliminated
|
|
14
|
+
* (filterPolyfillHelpers) and feature-gated centrally, matching how
|
|
15
|
+
* async_runtime / timer_methods are handled. The scheduler is STL-free, so it
|
|
16
|
+
* links on minimal-libc targets (Zephyr) where the std::function Promise
|
|
17
|
+
* runtime does not.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildCoopSchedulerPolyfill(_program: ProgramIR, config: AsyncRuntimeConfig, currentTimeExpr?: string): RuntimePolyfillIR | null;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { generateCoopScheduler } from "../api/shared/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a RuntimePolyfillIR for the no-STL cooperative scheduler, when the
|
|
4
|
+
* program has async/timer work AND the strategy has opted into priority/time-
|
|
5
|
+
* budget scheduling via AsyncRuntimeConfig.
|
|
6
|
+
*
|
|
7
|
+
* The polyfill carries only the scheduler namespace + CoopSched_run() alias.
|
|
8
|
+
* Per-program work-unit registration + dispatch lines are emitted separately
|
|
9
|
+
* by each strategy's asyncLoopInjection() via buildCoopSchedInjection().
|
|
10
|
+
*
|
|
11
|
+
* Why a polyfill and not shimLines(): polyfills are dead-code-eliminated
|
|
12
|
+
* (filterPolyfillHelpers) and feature-gated centrally, matching how
|
|
13
|
+
* async_runtime / timer_methods are handled. The scheduler is STL-free, so it
|
|
14
|
+
* links on minimal-libc targets (Zephyr) where the std::function Promise
|
|
15
|
+
* runtime does not.
|
|
16
|
+
*/
|
|
17
|
+
export function buildCoopSchedulerPolyfill(_program, config, currentTimeExpr = "millis()") {
|
|
18
|
+
if (!config.enablePriority && !config.enableTimeBudget)
|
|
19
|
+
return null;
|
|
20
|
+
return {
|
|
21
|
+
kind: "polyfill",
|
|
22
|
+
id: "coop_scheduler",
|
|
23
|
+
domain: "embedded",
|
|
24
|
+
// STL-free: only needs a 32-bit unsigned time source (millis() /
|
|
25
|
+
// k_uptime_get_32()) and fixed-width types, both available everywhere.
|
|
26
|
+
requiredIncludes: [],
|
|
27
|
+
forwardDeclarations: [],
|
|
28
|
+
helperStructs: [
|
|
29
|
+
generateCoopScheduler({
|
|
30
|
+
capacity: 8,
|
|
31
|
+
priorities: config.schedulerPriorities ?? 2,
|
|
32
|
+
enableTimeBudget: config.enableTimeBudget ?? false,
|
|
33
|
+
timeBudgetMs: config.timeBudgetMs ?? 5,
|
|
34
|
+
currentTimeExpr,
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
helperFunctions: [],
|
|
38
|
+
shimMacros: [],
|
|
39
|
+
dependencies: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -17,7 +17,9 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
17
17
|
overrideBaseName(originalBaseName: string, _outDirBaseName: string, _isEntryFile: boolean, _isNpmPackage: boolean): string;
|
|
18
18
|
effectiveEmitMode(requestedMode: string, _isNpmPackage: boolean): string;
|
|
19
19
|
normalizeCppType(typeName: string): string;
|
|
20
|
-
defaultNumericType(
|
|
20
|
+
defaultNumericType(compliance?: {
|
|
21
|
+
isBanned(ruleId: string): boolean;
|
|
22
|
+
}): string;
|
|
21
23
|
mapReturnType(_functionName: string, returnType: string): string;
|
|
22
24
|
isStringLikeType(cppType: string): boolean;
|
|
23
25
|
isPointerType(cppType: string): boolean;
|
|
@@ -54,8 +56,8 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
54
56
|
structFieldInitializer(_fieldValue: ExpressionIR, _compiletimeVarNames: Set<string>, _renderExpr: (e: ExpressionIR) => string): string | undefined;
|
|
55
57
|
getAsyncRuntimeConfig(): AsyncRuntimeConfig;
|
|
56
58
|
generateNativePolyfills(program: ProgramIR, ctx?: PlatformContext): RuntimePolyfillIR[];
|
|
57
|
-
asyncLoopInjection(
|
|
58
|
-
asyncLoopInjection(
|
|
59
|
+
asyncLoopInjection(taskVarNames: string[], config: AsyncRuntimeConfig): string[];
|
|
60
|
+
asyncLoopInjection(taskVarNames: string[], hasPromiseRuntime: boolean): string[];
|
|
59
61
|
asyncDriverFunctionName(): string;
|
|
60
62
|
shouldSkipTypeAlias(_cppType: string): boolean;
|
|
61
63
|
emitDiagnostics(_emitMode: string): Diagnostic[];
|
|
@@ -68,6 +70,7 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
68
70
|
close: string;
|
|
69
71
|
} | undefined;
|
|
70
72
|
getStdLibSupport(_architecture?: string): StdLibSupport;
|
|
73
|
+
debugMode(_target?: string): 'gdb' | 'printf';
|
|
71
74
|
generateDebugInitCode(): string[];
|
|
72
75
|
generateDebugBreakpointCode(params: {
|
|
73
76
|
fileName: string;
|
|
@@ -98,4 +101,13 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
98
101
|
supportedDisplayDrivers(): ReadonlySet<string>;
|
|
99
102
|
colorFormat(): "rgb565" | "rgb666" | "rgb888" | "mono";
|
|
100
103
|
graphicsCapacity(): GraphicsCapacity;
|
|
104
|
+
readDigitalPin(_pin: string): string;
|
|
105
|
+
readAnalogPin(_pin: string): string;
|
|
106
|
+
writeDigitalPin(_pin: string, _val: string): string;
|
|
107
|
+
setPinMode(_pin: string, _mode: string): string;
|
|
108
|
+
delayMs(_ms: string): string;
|
|
109
|
+
delayMicroseconds(_us: string): string;
|
|
110
|
+
halCallNames(): ReadonlySet<string>;
|
|
111
|
+
isHalCall(_name: string): boolean;
|
|
112
|
+
analogReadCallNames(): ReadonlySet<string>;
|
|
101
113
|
}
|