@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.9

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.
Files changed (167) hide show
  1. package/dist/api/config.d.ts +32 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/schema/types.d.ts +15 -0
  4. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  5. package/dist/api/shared/async-runtime-static.js +69 -24
  6. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  7. package/dist/api/shared/async-symbol-detector.js +140 -0
  8. package/dist/api/shared/async-types.d.ts +24 -0
  9. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  10. package/dist/api/shared/coop-scheduler.js +149 -0
  11. package/dist/api/shared/display-adapter.d.ts +2 -2
  12. package/dist/api/shared/display-adapter.js +9 -119
  13. package/dist/api/shared/display-adapters/sdl.js +29 -29
  14. package/dist/api/shared/display-profile.d.ts +38 -33
  15. package/dist/api/shared/display-profile.js +22 -170
  16. package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
  17. package/dist/api/shared/framework-manifest-registry.js +8 -6
  18. package/dist/api/shared/framework-manifest.d.ts +147 -28
  19. package/dist/api/shared/framework-manifest.js +35 -2
  20. package/dist/api/shared/graphics-strategy.d.ts +15 -6
  21. package/dist/api/shared/hal-op-ir.d.ts +340 -3
  22. package/dist/api/shared/hal-op-ir.js +34 -0
  23. package/dist/api/shared/index.d.ts +9 -1
  24. package/dist/api/shared/index.js +11 -1
  25. package/dist/api/shared/ir-core.d.ts +4 -0
  26. package/dist/api/shared/ir-declarations.d.ts +6 -0
  27. package/dist/api/shared/platform-strategy.d.ts +104 -2
  28. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  29. package/dist/api/shared/promise-runtime.d.ts +1 -1
  30. package/dist/api/shared/promise-runtime.js +17 -13
  31. package/dist/api/shared/toolchain-types.d.ts +17 -0
  32. package/dist/api/shared/validate-framework-manifest.js +77 -0
  33. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  34. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  35. package/dist/api/shared/worker-runtime.d.ts +69 -0
  36. package/dist/api/shared/worker-runtime.js +163 -0
  37. package/dist/cli.js +125 -121
  38. package/dist/config-loader.d.ts +7 -2
  39. package/dist/config-loader.js +20 -19
  40. package/dist/config-schema.d.ts +107 -65
  41. package/dist/config-schema.js +14 -0
  42. package/dist/contract/board-generator.d.ts +17 -0
  43. package/dist/contract/board-generator.js +57 -0
  44. package/dist/contract/contract-parser.d.ts +217 -0
  45. package/dist/contract/contract-parser.js +224 -0
  46. package/dist/contract/index.d.ts +22 -0
  47. package/dist/contract/index.js +68 -0
  48. package/dist/create/board-codegen.js +4 -4
  49. package/dist/create/board-generators.js +4 -5
  50. package/dist/create/board-spec.d.ts +74 -77
  51. package/dist/create/board-spec.js +0 -1
  52. package/dist/create/init-scaffold.d.ts +7 -3
  53. package/dist/create/init-scaffold.js +0 -62
  54. package/dist/create/init-templates.js +62 -16
  55. package/dist/create/init-wizard.js +32 -27
  56. package/dist/debug/preprocessor.js +178 -39
  57. package/dist/debug/types.d.ts +23 -0
  58. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  59. package/dist/diagnostics/mermaid-builder.js +34 -24
  60. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  61. package/dist/emit/compliance/arxml-writer.js +34 -0
  62. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  63. package/dist/emit/compliance/compliance-context.js +113 -0
  64. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  65. package/dist/emit/compliance/deviation-ledger.js +47 -0
  66. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  67. package/dist/emit/compliance/deviation-writer.js +37 -0
  68. package/dist/emit/compliance/index.d.ts +7 -0
  69. package/dist/emit/compliance/index.js +6 -0
  70. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  71. package/dist/emit/compliance/rule-engine.js +101 -0
  72. package/dist/emit/compliance/rules.d.ts +16 -0
  73. package/dist/emit/compliance/rules.js +191 -0
  74. package/dist/emit/compliance/types.d.ts +59 -0
  75. package/dist/emit/compliance/types.js +8 -0
  76. package/dist/emit/emitters/class-emitter.js +6 -1
  77. package/dist/emit/emitters/emitter-context.d.ts +23 -1
  78. package/dist/emit/emitters/function-emitter-impl.js +96 -6
  79. package/dist/emit/emitters/line-appender.js +19 -0
  80. package/dist/emit/emitters/line-marker.d.ts +38 -0
  81. package/dist/emit/emitters/line-marker.js +39 -0
  82. package/dist/emit/emitters/output-finalizer.js +80 -1
  83. package/dist/emit/emitters/setup.d.ts +17 -0
  84. package/dist/emit/emitters/setup.js +217 -36
  85. package/dist/emit/emitters/top-level-prep.js +6 -0
  86. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  87. package/dist/emit/emitters/ui-emitter.js +17 -8
  88. package/dist/emit/expression-renderer.d.ts +1 -1
  89. package/dist/emit/expression-renderer.js +81 -5
  90. package/dist/emit/route-hal-op.js +18 -5
  91. package/dist/emit/snprintf-helpers.js +7 -4
  92. package/dist/emit/statement-renderer.d.ts +10 -0
  93. package/dist/emit/statement-renderer.js +21 -5
  94. package/dist/emit/utils/async-state-machine.js +39 -12
  95. package/dist/framework-package.js +2 -0
  96. package/dist/framework-registry.d.ts +17 -0
  97. package/dist/install/framework-catalog.d.ts +53 -0
  98. package/dist/install/framework-catalog.js +107 -0
  99. package/dist/install/handle-install.d.ts +35 -0
  100. package/dist/install/handle-install.js +177 -0
  101. package/dist/install/index.d.ts +4 -0
  102. package/dist/install/index.js +3 -0
  103. package/dist/ir/adc-range-validation.js +14 -1
  104. package/dist/ir/build-ir-state.d.ts +1 -0
  105. package/dist/ir/build-ir-state.js +14 -0
  106. package/dist/ir/build-ir.js +4 -4
  107. package/dist/ir/call-graph.js +16 -0
  108. package/dist/ir/expression-to-ir.js +150 -0
  109. package/dist/ir/function-builder.js +22 -0
  110. package/dist/ir/hal/hal-emitter.js +10 -6
  111. package/dist/ir/hal/hal-parser.js +16 -9
  112. package/dist/ir/hal/hal-plugins.js +164 -3
  113. package/dist/ir/identifier-collector.js +21 -4
  114. package/dist/ir/interrupt-analysis.d.ts +5 -1
  115. package/dist/ir/interrupt-analysis.js +8 -16
  116. package/dist/ir/ownership-analysis.js +6 -0
  117. package/dist/ir/pin-capability-validation.js +37 -0
  118. package/dist/ir/pin-mode-validation.d.ts +2 -2
  119. package/dist/ir/pin-mode-validation.js +49 -18
  120. package/dist/ir/program-analysis.d.ts +42 -0
  121. package/dist/ir/program-analysis.js +243 -6
  122. package/dist/ir/render-expr.js +11 -0
  123. package/dist/ir/timing-validation.js +11 -2
  124. package/dist/ir/transformers/call-statement.js +105 -0
  125. package/dist/ir/transformers/expressions.js +4 -0
  126. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  127. package/dist/ir/transformers/namespace-methods.js +17 -12
  128. package/dist/ir/transformers/ui-reactive.js +2 -2
  129. package/dist/ir/transformers/variables.js +51 -0
  130. package/dist/ir/type-resolution.js +18 -0
  131. package/dist/ir/ui-element-auto-wire.js +7 -5
  132. package/dist/ir/utils/map-statements.d.ts +4 -0
  133. package/dist/ir/utils/map-statements.js +79 -0
  134. package/dist/ir/validation-orchestrator.js +6 -1
  135. package/dist/ir/worker-analysis.d.ts +10 -0
  136. package/dist/ir/worker-analysis.js +261 -0
  137. package/dist/orchestrator/graph-builder.js +8 -7
  138. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  139. package/dist/platform/coop-scheduler-runtime.js +41 -0
  140. package/dist/platform/generic-strategy.d.ts +15 -3
  141. package/dist/platform/generic-strategy.js +49 -4
  142. package/dist/safety/safety-bridge.d.ts +11 -0
  143. package/dist/safety/safety-bridge.js +48 -0
  144. package/dist/safety/sidecar-bridge.d.ts +5 -0
  145. package/dist/safety/sidecar-bridge.js +22 -0
  146. package/dist/safety-hook.d.ts +79 -0
  147. package/dist/safety-hook.js +35 -0
  148. package/dist/stores/display-profile-store.d.ts +0 -1
  149. package/dist/stores/display-profile-store.js +0 -1
  150. package/dist/testing.d.ts +4 -0
  151. package/dist/testing.js +5 -0
  152. package/dist/transpile.js +124 -35
  153. package/dist/types.d.ts +22 -2
  154. package/dist/ui-hook.d.ts +0 -2
  155. package/dist/utils/cli.d.ts +2 -2
  156. package/dist/utils/cli.js +68 -3
  157. package/package.json +20 -5
  158. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  159. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  160. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  161. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  162. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  163. package/dist/api/shared/display-adapters/st7796.js +0 -132
  164. package/dist/libdef/idf-discovery.d.ts +0 -7
  165. package/dist/libdef/idf-discovery.js +0 -59
  166. package/dist/licenses.d.ts +0 -185
  167. 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 (and the future @typecad/ui rename) it provides
163
- // compile-time authoring stubs only. ui.mount/signal/bind calls are
164
- // intercepted by tryResolveUICall and lowered to IR; the package itself
165
- // must NOT be emitted as a C++ module (it would synthesize a bogus
166
- // _ui_t struct for the `ui` namespace value).
167
- if (moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/ui") {
162
+ // Skip @typecad/ui and @typecad/safetythey 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(): string;
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(_taskVarNames: string[], config: AsyncRuntimeConfig): string[];
58
- asyncLoopInjection(_taskVarNames: string[], hasPromiseRuntime: boolean): string[];
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
  }