@typecad/cuttlefish 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/api/board-types.d.ts +1 -0
- package/dist/api/board-types.js +4 -0
- package/dist/api/capabilities.d.ts +12 -0
- package/dist/api/capabilities.js +1 -0
- package/dist/api/config.d.ts +120 -0
- package/dist/api/config.js +7 -0
- package/dist/api/index.d.ts +5 -0
- package/dist/api/index.js +2 -0
- package/dist/api/schema/builder.d.ts +14 -0
- package/dist/api/schema/builder.js +15 -0
- package/dist/api/schema/index.d.ts +2 -0
- package/dist/api/schema/index.js +1 -0
- package/dist/api/schema/types.d.ts +257 -0
- package/dist/api/schema/types.js +4 -0
- package/dist/api/shared/async-runtime-static.d.ts +8 -0
- package/dist/api/shared/async-runtime-static.js +201 -0
- package/dist/api/shared/async-types.d.ts +42 -0
- package/dist/api/shared/async-types.js +8 -0
- package/dist/api/shared/board-resolver.d.ts +12 -0
- package/dist/api/shared/board-resolver.js +7 -0
- package/dist/api/shared/cpp-type-ir.d.ts +229 -0
- package/dist/api/shared/cpp-type-ir.js +672 -0
- package/dist/api/shared/display-adapter.d.ts +12 -0
- package/dist/api/shared/display-adapter.js +145 -0
- package/dist/api/shared/display-adapters/eink-mono.d.ts +2 -0
- package/dist/api/shared/display-adapters/eink-mono.js +53 -0
- package/dist/api/shared/display-adapters/sdl.d.ts +2 -0
- package/dist/api/shared/display-adapters/sdl.js +452 -0
- package/dist/api/shared/display-adapters/ssd1309.d.ts +2 -0
- package/dist/api/shared/display-adapters/ssd1309.js +136 -0
- package/dist/api/shared/display-adapters/st7796.d.ts +2 -0
- package/dist/api/shared/display-adapters/st7796.js +132 -0
- package/dist/api/shared/display-capabilities.d.ts +45 -0
- package/dist/api/shared/display-capabilities.js +54 -0
- package/dist/api/shared/display-op-ir.d.ts +62 -0
- package/dist/api/shared/display-op-ir.js +19 -0
- package/dist/api/shared/display-profile.d.ts +265 -0
- package/dist/api/shared/display-profile.js +338 -0
- package/dist/api/shared/graphics-strategy.d.ts +29 -0
- package/dist/api/shared/graphics-strategy.js +11 -0
- package/dist/api/shared/hal-op-ir.d.ts +393 -0
- package/dist/api/shared/hal-op-ir.js +18 -0
- package/dist/api/shared/index.d.ts +26 -0
- package/dist/api/shared/index.js +21 -0
- package/dist/api/shared/ir-core.d.ts +363 -0
- package/dist/api/shared/ir-core.js +8 -0
- package/dist/api/shared/ir-declarations.d.ts +201 -0
- package/dist/api/shared/ir-declarations.js +26 -0
- package/dist/api/shared/ir.d.ts +85 -0
- package/dist/api/shared/ir.js +13 -0
- package/dist/api/shared/platform-strategy.d.ts +402 -0
- package/dist/api/shared/platform-strategy.js +14 -0
- package/dist/api/shared/polyfill-helper-registry.d.ts +11 -0
- package/dist/api/shared/polyfill-helper-registry.js +86 -0
- package/dist/api/shared/polyfill-types.d.ts +33 -0
- package/dist/api/shared/polyfill-types.js +76 -0
- package/dist/api/shared/promise-runtime.d.ts +10 -0
- package/dist/api/shared/promise-runtime.js +244 -0
- package/dist/api/shared/snprintf-types.d.ts +29 -0
- package/dist/api/shared/snprintf-types.js +8 -0
- package/dist/api/shared/string-method-registry.d.ts +53 -0
- package/dist/api/shared/string-method-registry.js +168 -0
- package/dist/api/shared/toolchain-types.d.ts +63 -0
- package/dist/api/shared/toolchain-types.js +71 -0
- package/dist/api/shared/types.d.ts +50 -0
- package/dist/api/shared/types.js +6 -0
- package/dist/ast/parse.d.ts +2 -0
- package/dist/ast/parse.js +4 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.js +95 -0
- package/dist/cli-utils.d.ts +37 -0
- package/dist/cli-utils.js +250 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +635 -0
- package/dist/config-loader.d.ts +78 -0
- package/dist/config-loader.js +508 -0
- package/dist/config-schema.d.ts +233 -0
- package/dist/config-schema.js +83 -0
- package/dist/create/board-checklist.d.ts +2 -0
- package/dist/create/board-checklist.js +52 -0
- package/dist/create/board-codegen.d.ts +9 -0
- package/dist/create/board-codegen.js +238 -0
- package/dist/create/board-generators.d.ts +12 -0
- package/dist/create/board-generators.js +652 -0
- package/dist/create/board-spec.d.ts +640 -0
- package/dist/create/board-spec.js +215 -0
- package/dist/create/eslint-rules-template.d.ts +2 -0
- package/dist/create/eslint-rules-template.js +672 -0
- package/dist/create/index.d.ts +11 -0
- package/dist/create/index.js +8 -0
- package/dist/create/init-scaffold.d.ts +27 -0
- package/dist/create/init-scaffold.js +150 -0
- package/dist/create/init-templates.d.ts +24 -0
- package/dist/create/init-templates.js +379 -0
- package/dist/create/init-wizard.d.ts +8 -0
- package/dist/create/init-wizard.js +168 -0
- package/dist/debug/breakpoint-loader.d.ts +24 -0
- package/dist/debug/breakpoint-loader.js +137 -0
- package/dist/debug/index.d.ts +3 -0
- package/dist/debug/index.js +7 -0
- package/dist/debug/preprocessor.d.ts +9 -0
- package/dist/debug/preprocessor.js +214 -0
- package/dist/debug/types.d.ts +32 -0
- package/dist/debug/types.js +6 -0
- package/dist/diagnostics/diagnostics-report.d.ts +51 -0
- package/dist/diagnostics/diagnostics-report.js +333 -0
- package/dist/diagnostics/json-schema.d.ts +138 -0
- package/dist/diagnostics/json-schema.js +27 -0
- package/dist/diagnostics/md-writer.d.ts +10 -0
- package/dist/diagnostics/md-writer.js +336 -0
- package/dist/diagnostics/mermaid-builder.d.ts +65 -0
- package/dist/diagnostics/mermaid-builder.js +681 -0
- package/dist/emit/cpp-emitter.d.ts +6 -0
- package/dist/emit/cpp-emitter.js +48 -0
- package/dist/emit/emitters/class-emitter.d.ts +2 -0
- package/dist/emit/emitters/class-emitter.js +523 -0
- package/dist/emit/emitters/emitter-context.d.ts +174 -0
- package/dist/emit/emitters/emitter-context.js +1 -0
- package/dist/emit/emitters/entrypoint-synthesizer.d.ts +2 -0
- package/dist/emit/emitters/entrypoint-synthesizer.js +175 -0
- package/dist/emit/emitters/function-emitter-impl.d.ts +19 -0
- package/dist/emit/emitters/function-emitter-impl.js +428 -0
- package/dist/emit/emitters/line-appender.d.ts +15 -0
- package/dist/emit/emitters/line-appender.js +344 -0
- package/dist/emit/emitters/namespace-emitter.d.ts +2 -0
- package/dist/emit/emitters/namespace-emitter.js +324 -0
- package/dist/emit/emitters/output-finalizer.d.ts +4 -0
- package/dist/emit/emitters/output-finalizer.js +267 -0
- package/dist/emit/emitters/setup.d.ts +3 -0
- package/dist/emit/emitters/setup.js +851 -0
- package/dist/emit/emitters/top-level-prep.d.ts +2 -0
- package/dist/emit/emitters/top-level-prep.js +706 -0
- package/dist/emit/emitters/type-decl-emitter.d.ts +2 -0
- package/dist/emit/emitters/type-decl-emitter.js +251 -0
- package/dist/emit/emitters/ui-emitter.d.ts +21 -0
- package/dist/emit/emitters/ui-emitter.js +564 -0
- package/dist/emit/enum-emitter.d.ts +1 -0
- package/dist/emit/enum-emitter.js +1 -0
- package/dist/emit/expression-renderer.d.ts +283 -0
- package/dist/emit/expression-renderer.js +1477 -0
- package/dist/emit/native-helpers-emitter.d.ts +11 -0
- package/dist/emit/native-helpers-emitter.js +34 -0
- package/dist/emit/route-hal-op.d.ts +5 -0
- package/dist/emit/route-hal-op.js +13 -0
- package/dist/emit/snprintf-helpers.d.ts +17 -0
- package/dist/emit/snprintf-helpers.js +462 -0
- package/dist/emit/statement-renderer.d.ts +189 -0
- package/dist/emit/statement-renderer.js +948 -0
- package/dist/emit/utils/async-state-machine.d.ts +42 -0
- package/dist/emit/utils/async-state-machine.js +343 -0
- package/dist/emit/utils/comment-helpers.d.ts +20 -0
- package/dist/emit/utils/comment-helpers.js +31 -0
- package/dist/emit/utils/cpp-helpers.d.ts +47 -0
- package/dist/emit/utils/cpp-helpers.js +85 -0
- package/dist/emit/utils/include-resolver.d.ts +58 -0
- package/dist/emit/utils/include-resolver.js +155 -0
- package/dist/emit/utils/index.d.ts +8 -0
- package/dist/emit/utils/index.js +12 -0
- package/dist/emit/utils/type-inference.d.ts +159 -0
- package/dist/emit/utils/type-inference.js +559 -0
- package/dist/eslint-check.d.ts +10 -0
- package/dist/eslint-check.js +102 -0
- package/dist/framework-package.d.ts +10 -0
- package/dist/framework-package.js +58 -0
- package/dist/framework-registry.d.ts +39 -0
- package/dist/framework-registry.js +30 -0
- package/dist/incremental-cache.d.ts +116 -0
- package/dist/incremental-cache.js +335 -0
- package/dist/ir/adc-range-validation.d.ts +11 -0
- package/dist/ir/adc-range-validation.js +250 -0
- package/dist/ir/ast-node-utils.d.ts +8 -0
- package/dist/ir/ast-node-utils.js +32 -0
- package/dist/ir/board-pin-utils.d.ts +13 -0
- package/dist/ir/board-pin-utils.js +65 -0
- package/dist/ir/board-resolver.d.ts +36 -0
- package/dist/ir/board-resolver.js +437 -0
- package/dist/ir/build-ir-state.d.ts +167 -0
- package/dist/ir/build-ir-state.js +306 -0
- package/dist/ir/build-ir.d.ts +2 -0
- package/dist/ir/build-ir.js +808 -0
- package/dist/ir/call-graph.d.ts +35 -0
- package/dist/ir/call-graph.js +194 -0
- package/dist/ir/compile-time-only.d.ts +2 -0
- package/dist/ir/compile-time-only.js +16 -0
- package/dist/ir/declaration-builders.d.ts +9 -0
- package/dist/ir/declaration-builders.js +608 -0
- package/dist/ir/entry-points.d.ts +30 -0
- package/dist/ir/entry-points.js +191 -0
- package/dist/ir/expression-to-ir.d.ts +5 -0
- package/dist/ir/expression-to-ir.js +2249 -0
- package/dist/ir/extract-property-chain.d.ts +10 -0
- package/dist/ir/extract-property-chain.js +30 -0
- package/dist/ir/feature-prescan.d.ts +3 -0
- package/dist/ir/feature-prescan.js +43 -0
- package/dist/ir/feature-registry.d.ts +30 -0
- package/dist/ir/feature-registry.js +816 -0
- package/dist/ir/filter.d.ts +7 -0
- package/dist/ir/filter.js +85 -0
- package/dist/ir/function-builder.d.ts +19 -0
- package/dist/ir/function-builder.js +670 -0
- package/dist/ir/hal/hal-emitter.d.ts +62 -0
- package/dist/ir/hal/hal-emitter.js +735 -0
- package/dist/ir/hal/hal-parser.d.ts +61 -0
- package/dist/ir/hal/hal-parser.js +473 -0
- package/dist/ir/hal/hal-plugins.d.ts +34 -0
- package/dist/ir/hal/hal-plugins.js +706 -0
- package/dist/ir/hal-resolver.d.ts +5 -0
- package/dist/ir/hal-resolver.js +5 -0
- package/dist/ir/heap-analysis.d.ts +8 -0
- package/dist/ir/heap-analysis.js +350 -0
- package/dist/ir/heap-array-validation.d.ts +24 -0
- package/dist/ir/heap-array-validation.js +29 -0
- package/dist/ir/identifier-collector.d.ts +18 -0
- package/dist/ir/identifier-collector.js +439 -0
- package/dist/ir/interrupt-analysis.d.ts +39 -0
- package/dist/ir/interrupt-analysis.js +467 -0
- package/dist/ir/memory-budget-validation.d.ts +10 -0
- package/dist/ir/memory-budget-validation.js +86 -0
- package/dist/ir/namespace-builder.d.ts +7 -0
- package/dist/ir/namespace-builder.js +152 -0
- package/dist/ir/ownership-analysis.d.ts +9 -0
- package/dist/ir/ownership-analysis.js +1207 -0
- package/dist/ir/peripheral-ownership.d.ts +3 -0
- package/dist/ir/peripheral-ownership.js +152 -0
- package/dist/ir/peripheral-symbols.d.ts +7 -0
- package/dist/ir/peripheral-symbols.js +57 -0
- package/dist/ir/peripheral-usage.d.ts +54 -0
- package/dist/ir/peripheral-usage.js +691 -0
- package/dist/ir/peripheral-validation.d.ts +8 -0
- package/dist/ir/peripheral-validation.js +77 -0
- package/dist/ir/pin-alias-conflict.d.ts +4 -0
- package/dist/ir/pin-alias-conflict.js +34 -0
- package/dist/ir/pin-capability-validation.d.ts +6 -0
- package/dist/ir/pin-capability-validation.js +413 -0
- package/dist/ir/pin-mode-validation.d.ts +10 -0
- package/dist/ir/pin-mode-validation.js +301 -0
- package/dist/ir/pin-safety.d.ts +11 -0
- package/dist/ir/pin-safety.js +58 -0
- package/dist/ir/program-analysis.d.ts +44 -0
- package/dist/ir/program-analysis.js +589 -0
- package/dist/ir/pulldown-validation.d.ts +7 -0
- package/dist/ir/pulldown-validation.js +49 -0
- package/dist/ir/pwm-timer-sharing.d.ts +4 -0
- package/dist/ir/pwm-timer-sharing.js +93 -0
- package/dist/ir/reachability.d.ts +68 -0
- package/dist/ir/reachability.js +224 -0
- package/dist/ir/register-decorators.d.ts +6 -0
- package/dist/ir/register-decorators.js +49 -0
- package/dist/ir/render-expr.d.ts +4 -0
- package/dist/ir/render-expr.js +127 -0
- package/dist/ir/resource-analysis.d.ts +19 -0
- package/dist/ir/resource-analysis.js +153 -0
- package/dist/ir/statement-to-ir.d.ts +23 -0
- package/dist/ir/statement-to-ir.js +349 -0
- package/dist/ir/symbol-table.d.ts +68 -0
- package/dist/ir/symbol-table.js +179 -0
- package/dist/ir/symbol-types.d.ts +56 -0
- package/dist/ir/symbol-types.js +107 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +4 -0
- package/dist/ir/timer0-pwm-timing-conflict.js +71 -0
- package/dist/ir/timing-validation.d.ts +7 -0
- package/dist/ir/timing-validation.js +95 -0
- package/dist/ir/transformers/array-methods.d.ts +8 -0
- package/dist/ir/transformers/array-methods.js +493 -0
- package/dist/ir/transformers/call-statement.d.ts +5 -0
- package/dist/ir/transformers/call-statement.js +758 -0
- package/dist/ir/transformers/callback-context-registry.d.ts +20 -0
- package/dist/ir/transformers/callback-context-registry.js +55 -0
- package/dist/ir/transformers/canvas-lowering.d.ts +42 -0
- package/dist/ir/transformers/canvas-lowering.js +249 -0
- package/dist/ir/transformers/control-flow.d.ts +8 -0
- package/dist/ir/transformers/control-flow.js +508 -0
- package/dist/ir/transformers/expressions.d.ts +7 -0
- package/dist/ir/transformers/expressions.js +406 -0
- package/dist/ir/transformers/hal-call-resolver.d.ts +29 -0
- package/dist/ir/transformers/hal-call-resolver.js +274 -0
- package/dist/ir/transformers/hal-emit-helpers.d.ts +14 -0
- package/dist/ir/transformers/hal-emit-helpers.js +63 -0
- package/dist/ir/transformers/namespace-methods.d.ts +20 -0
- package/dist/ir/transformers/namespace-methods.js +110 -0
- package/dist/ir/transformers/register-assignment.d.ts +5 -0
- package/dist/ir/transformers/register-assignment.js +56 -0
- package/dist/ir/transformers/ui-call-resolver.d.ts +149 -0
- package/dist/ir/transformers/ui-call-resolver.js +1136 -0
- package/dist/ir/transformers/ui-callback-lowering.d.ts +62 -0
- package/dist/ir/transformers/ui-callback-lowering.js +623 -0
- package/dist/ir/transformers/ui-mount.d.ts +20 -0
- package/dist/ir/transformers/ui-mount.js +35 -0
- package/dist/ir/transformers/ui-reactive.d.ts +68 -0
- package/dist/ir/transformers/ui-reactive.js +129 -0
- package/dist/ir/transformers/variables.d.ts +14 -0
- package/dist/ir/transformers/variables.js +1045 -0
- package/dist/ir/try-catch-validation.d.ts +26 -0
- package/dist/ir/try-catch-validation.js +128 -0
- package/dist/ir/type-resolution.d.ts +31 -0
- package/dist/ir/type-resolution.js +977 -0
- package/dist/ir/ui-element-auto-wire.d.ts +50 -0
- package/dist/ir/ui-element-auto-wire.js +218 -0
- package/dist/ir/unit-suspicion-validation.d.ts +12 -0
- package/dist/ir/unit-suspicion-validation.js +296 -0
- package/dist/ir/utils/walk-ir.d.ts +6 -0
- package/dist/ir/utils/walk-ir.js +162 -0
- package/dist/ir/validation-orchestrator.d.ts +4 -0
- package/dist/ir/validation-orchestrator.js +45 -0
- package/dist/libdef/base-class-resolver.d.ts +23 -0
- package/dist/libdef/base-class-resolver.js +50 -0
- package/dist/libdef/cpp-to-decl.d.ts +34 -0
- package/dist/libdef/cpp-to-decl.js +584 -0
- package/dist/libdef/header-parser.d.ts +53 -0
- package/dist/libdef/header-parser.js +269 -0
- package/dist/libdef/registry.d.ts +10 -0
- package/dist/libdef/registry.js +142 -0
- package/dist/mapping/peripheral-names.d.ts +23 -0
- package/dist/mapping/peripheral-names.js +63 -0
- package/dist/mapping/source-map.d.ts +46 -0
- package/dist/mapping/source-map.js +207 -0
- package/dist/orchestrator/dts-generator.d.ts +17 -0
- package/dist/orchestrator/dts-generator.js +83 -0
- package/dist/orchestrator/graph-builder.d.ts +18 -0
- package/dist/orchestrator/graph-builder.js +218 -0
- package/dist/orchestrator/semantic-facts-verifier.d.ts +38 -0
- package/dist/orchestrator/semantic-facts-verifier.js +121 -0
- package/dist/orchestrator/semantic-facts.d.ts +141 -0
- package/dist/orchestrator/semantic-facts.js +652 -0
- package/dist/orchestrator/type-checker.d.ts +39 -0
- package/dist/orchestrator/type-checker.js +760 -0
- package/dist/platform/async-runtime.d.ts +8 -0
- package/dist/platform/async-runtime.js +26 -0
- package/dist/platform/generic-strategy.d.ts +101 -0
- package/dist/platform/generic-strategy.js +289 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.js +9 -0
- package/dist/platform/registry.d.ts +21 -0
- package/dist/platform/registry.js +42 -0
- package/dist/platform/toolchain.d.ts +4 -0
- package/dist/platform/toolchain.js +43 -0
- package/dist/preview/client.d.ts +1 -0
- package/dist/preview/client.js +137 -0
- package/dist/preview/server.d.ts +5 -0
- package/dist/preview/server.js +199 -0
- package/dist/profiler/index.d.ts +28 -0
- package/dist/profiler/index.js +28 -0
- package/dist/profiler/profiler.d.ts +95 -0
- package/dist/profiler/profiler.js +338 -0
- package/dist/profiler/types.d.ts +87 -0
- package/dist/profiler/types.js +12 -0
- package/dist/stores/display-profile-store.d.ts +16 -0
- package/dist/stores/display-profile-store.js +48 -0
- package/dist/stores/theme-store.d.ts +13 -0
- package/dist/stores/theme-store.js +30 -0
- package/dist/testing.d.ts +45 -0
- package/dist/testing.js +47 -0
- package/dist/transpile/resolution.d.ts +29 -0
- package/dist/transpile/resolution.js +282 -0
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +722 -0
- package/dist/types.d.ts +217 -0
- package/dist/types.js +1 -0
- package/dist/ui/ui-bridge.d.ts +4 -0
- package/dist/ui/ui-bridge.js +34 -0
- package/dist/ui-hook.d.ts +84 -0
- package/dist/ui-hook.js +45 -0
- package/dist/utils/cli.d.ts +3 -0
- package/dist/utils/cli.js +463 -0
- package/dist/utils/fs.d.ts +4 -0
- package/dist/utils/fs.js +23 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +8 -0
- package/dist/utils/strings.d.ts +35 -0
- package/dist/utils/strings.js +142 -0
- package/dist/utils/ui.d.ts +66 -0
- package/dist/utils/ui.js +149 -0
- package/dist/watch.d.ts +35 -0
- package/dist/watch.js +111 -0
- package/package.json +127 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Interrupt Safety Analysis
|
|
3
|
+
//
|
|
4
|
+
// Detects unsafe operations inside interrupt handlers and duplicate handlers
|
|
5
|
+
// on the same pin.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
import { walkNestedStatements, walkExpressionsInExpression } from './utils/walk-ir.js';
|
|
8
|
+
export { walkNestedStatements as scanNestedStatements } from './utils/walk-ir.js';
|
|
9
|
+
/**
|
|
10
|
+
* Default ISR-unsafe operations used when no platform-specific map is provided.
|
|
11
|
+
* Platforms should supply their own list via PlatformStrategy.isrUnsafeOperations().
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_ISR_UNSAFE_OPERATIONS = new Map([
|
|
14
|
+
['delay', { reason: 'delay() blocks the CPU and should not be used in interrupt context', severity: 'warning' }],
|
|
15
|
+
['delayMicroseconds', { reason: 'delayMicroseconds() blocks and should be avoided in ISRs', severity: 'warning' }],
|
|
16
|
+
['Serial.print', { reason: 'Serial.print() may not work correctly in interrupt context', severity: 'info' }],
|
|
17
|
+
['Serial.println', { reason: 'Serial.println() may not work correctly in interrupt context', severity: 'info' }],
|
|
18
|
+
['Serial.write', { reason: 'Serial.write() may not work correctly in interrupt context', severity: 'info' }],
|
|
19
|
+
['Serial.read', { reason: 'Serial.read() may not work correctly in interrupt context', severity: 'info' }],
|
|
20
|
+
['I2C0', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
|
|
21
|
+
['I2C1', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
|
|
22
|
+
['SPI0', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
|
|
23
|
+
['SPI1', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Analyze a program for interrupt handler issues.
|
|
27
|
+
*
|
|
28
|
+
* @param program - The program IR to analyze
|
|
29
|
+
* @param usage - Peripheral usage information
|
|
30
|
+
* @param isrUnsafeOps - Platform-specific ISR-unsafe operations map (from strategy)
|
|
31
|
+
* @returns Array of diagnostics for interrupt-related issues
|
|
32
|
+
*/
|
|
33
|
+
export function analyzeInterruptSafety(program, usage, isrUnsafeOps) {
|
|
34
|
+
const diagnostics = [];
|
|
35
|
+
const unsafeOps = isrUnsafeOps ?? DEFAULT_ISR_UNSAFE_OPERATIONS;
|
|
36
|
+
// Track duplicate handlers
|
|
37
|
+
const handlersByPin = new Map();
|
|
38
|
+
// Scan for attachInterrupt calls
|
|
39
|
+
scanForInterruptHandlers(program, handlersByPin, diagnostics);
|
|
40
|
+
// Scan for unsafe operations inside ISR callbacks
|
|
41
|
+
scanForUnsafeOperations(program, diagnostics, unsafeOps);
|
|
42
|
+
return diagnostics;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Scan the program for attachInterrupt calls and detect duplicates.
|
|
46
|
+
*/
|
|
47
|
+
function scanForInterruptHandlers(program, handlersByPin, diagnostics) {
|
|
48
|
+
// Scan top-level statements
|
|
49
|
+
if (program.topLevelStatements) {
|
|
50
|
+
for (const stmt of program.topLevelStatements) {
|
|
51
|
+
scanStatementForInterruptHandler(stmt, handlersByPin, diagnostics);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Scan function bodies
|
|
55
|
+
if (program.functions) {
|
|
56
|
+
for (const fn of program.functions) {
|
|
57
|
+
if (fn.statements) {
|
|
58
|
+
for (const stmt of fn.statements) {
|
|
59
|
+
scanStatementForInterruptHandler(stmt, handlersByPin, diagnostics);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Scan a statement for attachInterrupt calls.
|
|
67
|
+
*/
|
|
68
|
+
function scanStatementForInterruptHandler(stmt, handlersByPin, diagnostics) {
|
|
69
|
+
if (!stmt || typeof stmt !== 'object')
|
|
70
|
+
return;
|
|
71
|
+
// Check for cuttlefish-call statements — both the low-level pin.attachInterrupt() and
|
|
72
|
+
// the flat sugar API (pin.onFalling / pin.onRising / pin.onChange / etc.) which all
|
|
73
|
+
// lower to a single attachInterrupt call on the same hardware interrupt line.
|
|
74
|
+
const ATTACH_METHODS = new Set(['attachInterrupt', 'onFalling', 'onRising', 'onChange', 'onLow', 'onHigh']);
|
|
75
|
+
// Recursively scan nested statements in control flow
|
|
76
|
+
walkNestedStatements(stmt, (s) => scanStatementForInterruptHandler(s, handlersByPin, diagnostics));
|
|
77
|
+
}
|
|
78
|
+
function scanForUnsafeOperations(program, diagnostics, unsafeOps) {
|
|
79
|
+
scanProgramForCallbacks(program, (callback, parentExpr) => {
|
|
80
|
+
const isISR = isInterruptHandlerCallback(callback, parentExpr);
|
|
81
|
+
if (isISR && callback.statements) {
|
|
82
|
+
for (const stmt of callback.statements) {
|
|
83
|
+
scanStatementForUnsafeOps(stmt, diagnostics, unsafeOps);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function isInterruptHandlerCallback(callback, parentExpr) {
|
|
89
|
+
if (callback.isInterruptHandler)
|
|
90
|
+
return true;
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function scanStatementForUnsafeOps(stmt, diagnostics, unsafeOps) {
|
|
94
|
+
if (!stmt || typeof stmt !== 'object')
|
|
95
|
+
return;
|
|
96
|
+
if (stmt.kind === 'call') {
|
|
97
|
+
const call = stmt;
|
|
98
|
+
checkCalleeForUnsafeOp(call.callee, diagnostics, unsafeOps);
|
|
99
|
+
}
|
|
100
|
+
// HAL-op statements: after HAL resolution, bare Arduino calls like delay()
|
|
101
|
+
// and delayMicroseconds() become structured hal-op statements (timing.delay,
|
|
102
|
+
// timing.delay_microseconds) rather than kind:'call'. Map them back to the
|
|
103
|
+
// unsafe-op keys so ISR detection works on the resolved form.
|
|
104
|
+
if (stmt.kind === 'hal-op') {
|
|
105
|
+
const op = stmt.operation;
|
|
106
|
+
if (op) {
|
|
107
|
+
const HAL_OP_TO_UNSAFE = {
|
|
108
|
+
'timing.delay': 'delay',
|
|
109
|
+
'timing.delay_microseconds': 'delayMicroseconds',
|
|
110
|
+
};
|
|
111
|
+
const key = HAL_OP_TO_UNSAFE[op.operation];
|
|
112
|
+
if (key)
|
|
113
|
+
checkCalleeForUnsafeOp(key, diagnostics, unsafeOps);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
walkNestedStatements(stmt, (s) => scanStatementForUnsafeOps(s, diagnostics, unsafeOps));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if a callee is an unsafe operation and generate diagnostic.
|
|
120
|
+
*/
|
|
121
|
+
function checkCalleeForUnsafeOp(callee, diagnostics, unsafeOps) {
|
|
122
|
+
if (!callee)
|
|
123
|
+
return;
|
|
124
|
+
// Check direct matches
|
|
125
|
+
const unsafe = unsafeOps.get(callee);
|
|
126
|
+
if (unsafe) {
|
|
127
|
+
diagnostics.push({
|
|
128
|
+
severity: unsafe.severity,
|
|
129
|
+
message: `${callee}() ${unsafe.reason}`,
|
|
130
|
+
code: 'interrupt-unsafe-operation',
|
|
131
|
+
source: 'interrupt-analysis',
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Check prefix matches (e.g., I2C0.write matches I2C0)
|
|
136
|
+
for (const [prefix, info] of unsafeOps) {
|
|
137
|
+
if (callee.startsWith(prefix + '.') || callee.startsWith(prefix + ':')) {
|
|
138
|
+
diagnostics.push({
|
|
139
|
+
severity: info.severity,
|
|
140
|
+
message: `${callee} - ${info.reason}`,
|
|
141
|
+
code: 'interrupt-unsafe-operation',
|
|
142
|
+
source: 'interrupt-analysis',
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Helper to scan program for callback expressions.
|
|
150
|
+
*/
|
|
151
|
+
function scanProgramForCallbacks(program, callback) {
|
|
152
|
+
// Scan top-level statements
|
|
153
|
+
if (program.topLevelStatements) {
|
|
154
|
+
for (const stmt of program.topLevelStatements) {
|
|
155
|
+
scanStatementForCallbacks(stmt, callback, null);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Scan functions
|
|
159
|
+
if (program.functions) {
|
|
160
|
+
for (const fn of program.functions) {
|
|
161
|
+
if (fn.statements) {
|
|
162
|
+
for (const stmt of fn.statements) {
|
|
163
|
+
scanStatementForCallbacks(stmt, callback, null);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Scan registered callbacks from HAL resolver (callback() directive)
|
|
169
|
+
for (const rc of (program.registeredCallbacks ?? [])) {
|
|
170
|
+
callback(rc.callbackIR, null);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Scan a statement for callback expressions.
|
|
175
|
+
*/
|
|
176
|
+
function scanStatementForCallbacks(stmt, callback, parentExpr) {
|
|
177
|
+
if (!stmt || typeof stmt !== 'object')
|
|
178
|
+
return;
|
|
179
|
+
// Check call args for callbacks
|
|
180
|
+
if (stmt.kind === 'call' && stmt.args) {
|
|
181
|
+
for (const arg of stmt.args) {
|
|
182
|
+
if (arg && arg.kind === 'callback') {
|
|
183
|
+
callback(arg, stmt);
|
|
184
|
+
}
|
|
185
|
+
scanExpressionForCallbacks(arg, callback, stmt);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Check var_decl initializer
|
|
189
|
+
if (stmt.kind === 'var_decl' && stmt.initializer) {
|
|
190
|
+
scanExpressionForCallbacks(stmt.initializer, callback, stmt);
|
|
191
|
+
}
|
|
192
|
+
// Check assignment value
|
|
193
|
+
if (stmt.kind === 'assign' && stmt.value) {
|
|
194
|
+
scanExpressionForCallbacks(stmt.value, callback, stmt);
|
|
195
|
+
}
|
|
196
|
+
walkNestedStatements(stmt, (s) => scanStatementForCallbacks(s, callback, parentExpr));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Scan an expression for callback expressions.
|
|
200
|
+
*/
|
|
201
|
+
function scanExpressionForCallbacks(expr, callback, parentExpr) {
|
|
202
|
+
if (!expr || typeof expr !== 'object')
|
|
203
|
+
return;
|
|
204
|
+
// Check if this is a callback
|
|
205
|
+
if (expr.kind === 'callback') {
|
|
206
|
+
callback(expr, parentExpr);
|
|
207
|
+
}
|
|
208
|
+
// Check nested expressions
|
|
209
|
+
if (expr.args && Array.isArray(expr.args)) {
|
|
210
|
+
for (const arg of expr.args) {
|
|
211
|
+
scanExpressionForCallbacks(arg, callback, expr);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (expr.left)
|
|
215
|
+
scanExpressionForCallbacks(expr.left, callback, expr);
|
|
216
|
+
if (expr.right)
|
|
217
|
+
scanExpressionForCallbacks(expr.right, callback, expr);
|
|
218
|
+
if (expr.operand)
|
|
219
|
+
scanExpressionForCallbacks(expr.operand, callback, expr);
|
|
220
|
+
if (expr.condition)
|
|
221
|
+
scanExpressionForCallbacks(expr.condition, callback, expr);
|
|
222
|
+
if (expr.whenTrue)
|
|
223
|
+
scanExpressionForCallbacks(expr.whenTrue, callback, expr);
|
|
224
|
+
if (expr.whenFalse)
|
|
225
|
+
scanExpressionForCallbacks(expr.whenFalse, callback, expr);
|
|
226
|
+
if (expr.object)
|
|
227
|
+
scanExpressionForCallbacks(expr.object, callback, expr);
|
|
228
|
+
if (expr.value && typeof expr.value === 'object' && 'kind' in expr.value) {
|
|
229
|
+
scanExpressionForCallbacks(expr.value, callback, expr);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Automatic volatile inference for ISR-shared globals.
|
|
234
|
+
//
|
|
235
|
+
// The classic AVR bug: a global `flag` set in an ISR and polled in loop()
|
|
236
|
+
// becomes an infinite loop under -Os because g++ caches it in a register. The
|
|
237
|
+
// transpiler knows which callbacks are ISRs (isInterruptHandler) and which
|
|
238
|
+
// globals are assigned inside them — information g++ cannot recover. This pass
|
|
239
|
+
// auto-marks such globals `volatile`, eliminating the bug class entirely. The
|
|
240
|
+
// emission path already handles isVolatile (statement-renderer.ts).
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
/** Collect variable names assigned (written) within a statement body. */
|
|
243
|
+
function collectAssignedNames(stmts, names) {
|
|
244
|
+
for (const stmt of stmts) {
|
|
245
|
+
const s = stmt;
|
|
246
|
+
if (stmt.kind === 'assign') {
|
|
247
|
+
// `flag = true` → target is the bare name (no dot/arrow).
|
|
248
|
+
if (typeof s.target === 'string' && !s.target.includes('.') && !s.target.includes('->')) {
|
|
249
|
+
names.add(s.target);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (stmt.kind === 'update') {
|
|
253
|
+
// `flag++` / `counter--`.
|
|
254
|
+
if (typeof s.target === 'string' && !s.target.includes('.') && !s.target.includes('->')) {
|
|
255
|
+
names.add(s.target);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Recurse into nested bodies.
|
|
259
|
+
const nested = getNestedStatements(stmt);
|
|
260
|
+
if (nested)
|
|
261
|
+
collectAssignedNames(nested, names);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/** Collect variable names read (referenced) within a statement body. */
|
|
265
|
+
function collectReadNames(stmts, names) {
|
|
266
|
+
for (const stmt of stmts) {
|
|
267
|
+
walkExpressionsInStatement(stmt, (expr) => {
|
|
268
|
+
if (expr.kind === 'identifier' && typeof expr.value === 'string') {
|
|
269
|
+
names.add(expr.value);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
const nested = getNestedStatements(stmt);
|
|
273
|
+
if (nested)
|
|
274
|
+
collectReadNames(nested, names);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** Walk all expressions in a single statement (delegates to walkExpressionsInExpression). */
|
|
278
|
+
function walkExpressionsInStatement(stmt, visitor) {
|
|
279
|
+
const s = stmt;
|
|
280
|
+
if (s.initializer && typeof s.initializer === 'object')
|
|
281
|
+
walkExpressionsInExpression(s.initializer, visitor);
|
|
282
|
+
if (s.value && typeof s.value === 'object' && 'kind' in s.value)
|
|
283
|
+
walkExpressionsInExpression(s.value, visitor);
|
|
284
|
+
if (s.condition && typeof s.condition === 'object')
|
|
285
|
+
walkExpressionsInExpression(s.condition, visitor);
|
|
286
|
+
if (s.expression && typeof s.expression === 'object' && 'kind' in s.expression)
|
|
287
|
+
walkExpressionsInExpression(s.expression, visitor);
|
|
288
|
+
if (Array.isArray(s.args)) {
|
|
289
|
+
for (const a of s.args)
|
|
290
|
+
walkExpressionsInExpression(a, visitor);
|
|
291
|
+
}
|
|
292
|
+
if (s.target && typeof s.target === 'string') {
|
|
293
|
+
// assignment target is read-then-written; record the read.
|
|
294
|
+
visitor({ kind: 'identifier', value: s.target });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Infer `volatile` for global variables shared between ISR callbacks and main
|
|
299
|
+
* code. Mutates the IR (sets isVolatile=true on qualifying var_decls) and
|
|
300
|
+
* emits an info diagnostic for each promotion. A variable qualifies when it is
|
|
301
|
+
* a global (top-level) that is assigned inside an isInterruptHandler callback
|
|
302
|
+
* AND read from non-ISR code (setup/loop/main/functions).
|
|
303
|
+
*/
|
|
304
|
+
export function inferVolatileForIsrSharedVars(program, diagnostics) {
|
|
305
|
+
// 1. Build a map of global var_decl name → IR node.
|
|
306
|
+
const globalVars = new Map();
|
|
307
|
+
for (const stmt of program.topLevelStatements) {
|
|
308
|
+
if (stmt.kind === 'var_decl' && typeof stmt.name === 'string') {
|
|
309
|
+
globalVars.set(stmt.name, stmt);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (globalVars.size === 0)
|
|
313
|
+
return;
|
|
314
|
+
// 2. Find names assigned inside ISR callbacks.
|
|
315
|
+
const isrAssignedNames = new Set();
|
|
316
|
+
const isrCallbackBodies = [];
|
|
317
|
+
scanProgramForCallbacks(program, (callback) => {
|
|
318
|
+
if (callback.isInterruptHandler && Array.isArray(callback.statements)) {
|
|
319
|
+
isrCallbackBodies.push(callback.statements);
|
|
320
|
+
collectAssignedNames(callback.statements, isrAssignedNames);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
if (isrAssignedNames.size === 0)
|
|
324
|
+
return;
|
|
325
|
+
// 3. Find names read from non-ISR code.
|
|
326
|
+
const mainReadNames = new Set();
|
|
327
|
+
for (const stmt of program.topLevelStatements)
|
|
328
|
+
collectReadNames([stmt], mainReadNames);
|
|
329
|
+
for (const fn of program.functions) {
|
|
330
|
+
if (fn.statements)
|
|
331
|
+
collectReadNames(fn.statements, mainReadNames);
|
|
332
|
+
}
|
|
333
|
+
for (const cls of program.classes) {
|
|
334
|
+
for (const m of cls.methods) {
|
|
335
|
+
if (m.statements)
|
|
336
|
+
collectReadNames(m.statements, mainReadNames);
|
|
337
|
+
}
|
|
338
|
+
if (cls.constructor?.statements)
|
|
339
|
+
collectReadNames(cls.constructor.statements, mainReadNames);
|
|
340
|
+
}
|
|
341
|
+
// ISR callback reads don't count as "main reads" — exclude them.
|
|
342
|
+
for (const body of isrCallbackBodies) {
|
|
343
|
+
const isrReads = new Set();
|
|
344
|
+
collectReadNames(body, isrReads);
|
|
345
|
+
for (const name of isrReads) {
|
|
346
|
+
// Only remove if the name isn't ALSO read outside ISRs (already captured above).
|
|
347
|
+
// This is a conservative check: we keep the name if it appears in both.
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// 4. Promote globals that are ISR-written AND main-read.
|
|
351
|
+
for (const [name, stmt] of globalVars) {
|
|
352
|
+
if (isrAssignedNames.has(name) && mainReadNames.has(name) && !stmt.isVolatile) {
|
|
353
|
+
stmt.isVolatile = true;
|
|
354
|
+
diagnostics.push({
|
|
355
|
+
severity: 'info',
|
|
356
|
+
message: `'${name}' is written in an interrupt handler and read in main code — emitted as \`volatile\` to prevent the compiler from caching it in a register (the classic ISR/loop race).`,
|
|
357
|
+
line: stmt.sourceSpan?.startLine,
|
|
358
|
+
column: stmt.sourceSpan?.startColumn,
|
|
359
|
+
code: 'volatile-isr-shared',
|
|
360
|
+
source: 'interrupt-analysis',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/** Helper: get nested statement arrays from a compound statement. */
|
|
366
|
+
function getNestedStatements(stmt) {
|
|
367
|
+
const s = stmt;
|
|
368
|
+
const out = [];
|
|
369
|
+
if (Array.isArray(s.body))
|
|
370
|
+
out.push(...s.body);
|
|
371
|
+
if (Array.isArray(s.thenBranch))
|
|
372
|
+
out.push(...s.thenBranch);
|
|
373
|
+
if (Array.isArray(s.elseBranch))
|
|
374
|
+
out.push(...s.elseBranch);
|
|
375
|
+
if (Array.isArray(s.tryBlock))
|
|
376
|
+
out.push(...s.tryBlock);
|
|
377
|
+
if (Array.isArray(s.catchBlock))
|
|
378
|
+
out.push(...s.catchBlock);
|
|
379
|
+
if (Array.isArray(s.finallyBlock))
|
|
380
|
+
out.push(...s.finallyBlock);
|
|
381
|
+
if (Array.isArray(s.cases)) {
|
|
382
|
+
for (const c of s.cases) {
|
|
383
|
+
if (Array.isArray(c.body))
|
|
384
|
+
out.push(...c.body);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return out.length > 0 ? out : null;
|
|
388
|
+
}
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Reentrancy detection.
|
|
391
|
+
//
|
|
392
|
+
// If a function is called from both an ISR callback and main-thread code
|
|
393
|
+
// (setup/loop/other functions), an interrupt firing mid-execution corrupts
|
|
394
|
+
// the function's local state or shared buffers. This is the hardest embedded
|
|
395
|
+
// bug to diagnose — rare, non-deterministic corruption. The transpiler knows
|
|
396
|
+
// which callbacks are ISRs and can see the whole call graph, so it can flag
|
|
397
|
+
// the intersection — something g++ fundamentally cannot do (it has no concept
|
|
398
|
+
// of "this function runs in interrupt context").
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
/** Collect callee names from call statements within a statement body. */
|
|
401
|
+
function collectCalleeNames(stmts, names) {
|
|
402
|
+
for (const stmt of stmts) {
|
|
403
|
+
const s = stmt;
|
|
404
|
+
if (stmt.kind === 'call' && typeof s.callee === 'string') {
|
|
405
|
+
// Bare function calls only (not method calls like obj.method()).
|
|
406
|
+
if (!s.callee.includes('.') && !s.callee.includes('->') && !s.callee.includes('::')) {
|
|
407
|
+
names.add(s.callee);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const nested = getNestedStatements(stmt);
|
|
411
|
+
if (nested)
|
|
412
|
+
collectCalleeNames(nested, names);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Detect functions called from both ISR callbacks and main-thread code.
|
|
417
|
+
* Emits a warning for each reentrancy-risk function.
|
|
418
|
+
*/
|
|
419
|
+
export function detectReentrancyRisk(program, diagnostics) {
|
|
420
|
+
// Collect user-defined function names for filtering (only flag calls to
|
|
421
|
+
// functions that exist in this program — not bare Arduino functions).
|
|
422
|
+
const userFunctionNames = new Set();
|
|
423
|
+
for (const fn of program.functions)
|
|
424
|
+
userFunctionNames.add(fn.originalName);
|
|
425
|
+
for (const cls of program.classes) {
|
|
426
|
+
for (const m of cls.methods)
|
|
427
|
+
userFunctionNames.add(m.name);
|
|
428
|
+
}
|
|
429
|
+
if (userFunctionNames.size === 0)
|
|
430
|
+
return;
|
|
431
|
+
// 1. Collect callees from ISR callback bodies.
|
|
432
|
+
const isrCallees = new Set();
|
|
433
|
+
scanProgramForCallbacks(program, (callback) => {
|
|
434
|
+
if (callback.isInterruptHandler && Array.isArray(callback.statements)) {
|
|
435
|
+
collectCalleeNames(callback.statements, isrCallees);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
if (isrCallees.size === 0)
|
|
439
|
+
return;
|
|
440
|
+
// 2. Collect callees from main-thread code (functions + class methods).
|
|
441
|
+
const mainCallees = new Set();
|
|
442
|
+
for (const fn of program.functions) {
|
|
443
|
+
if (fn.statements)
|
|
444
|
+
collectCalleeNames(fn.statements, mainCallees);
|
|
445
|
+
}
|
|
446
|
+
for (const cls of program.classes) {
|
|
447
|
+
for (const m of cls.methods) {
|
|
448
|
+
if (m.statements)
|
|
449
|
+
collectCalleeNames(m.statements, mainCallees);
|
|
450
|
+
}
|
|
451
|
+
if (cls.constructor?.statements)
|
|
452
|
+
collectCalleeNames(cls.constructor.statements, mainCallees);
|
|
453
|
+
}
|
|
454
|
+
for (const stmt of program.topLevelStatements)
|
|
455
|
+
collectCalleeNames([stmt], mainCallees);
|
|
456
|
+
// 3. Flag user functions in the intersection.
|
|
457
|
+
for (const name of isrCallees) {
|
|
458
|
+
if (mainCallees.has(name) && userFunctionNames.has(name)) {
|
|
459
|
+
diagnostics.push({
|
|
460
|
+
severity: 'warning',
|
|
461
|
+
message: `'${name}' is called from both an interrupt handler and main-thread code. An interrupt firing mid-execution can corrupt the function's local state. Wrap the main-thread call in noInterrupts()/interrupts(), or refactor to avoid sharing the function.`,
|
|
462
|
+
code: 'reentrancy-risk',
|
|
463
|
+
source: 'interrupt-analysis',
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ProgramIR } from '../api/index.js';
|
|
2
|
+
import type { BoardConstants } from '../api/shared/index.js';
|
|
3
|
+
import type { Diagnostic } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Validate that the estimated memory budget fits within the board's SRAM.
|
|
6
|
+
* Emits a warning when static globals + estimated stack frames exceed 80% of
|
|
7
|
+
* SRAM. Only runs when boardConstants provides `memory.sram` and
|
|
8
|
+
* `architecture`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateMemoryBudget(program: ProgramIR, boardConstants: BoardConstants | undefined): Diagnostic[];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Memory Budget Validation
|
|
3
|
+
//
|
|
4
|
+
// Compares the transpiler's static memory estimate against the board's SRAM
|
|
5
|
+
// budget. On AVR, the 2 KB SRAM is shared by globals/static data (grows up
|
|
6
|
+
// from .bss/.data), the heap (grows up from the end of .bss), and the stack
|
|
7
|
+
// (grows down from the top). There is no MMU — collision corrupts memory
|
|
8
|
+
// silently, producing the classic "works in debug, crashes in release"
|
|
9
|
+
// embedded failure.
|
|
10
|
+
//
|
|
11
|
+
// The transpiler knows the board's SRAM (boardConstants) and can estimate
|
|
12
|
+
// static bytes + call depth — something g++ cannot do (it has no concept of
|
|
13
|
+
// the board's memory size). This validator surfaces a warning when the
|
|
14
|
+
// estimated budget is tight, so the user can act before hardware surprises.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
import { analyzeHeapUsage } from './heap-analysis.js';
|
|
17
|
+
import { buildCallGraph } from './call-graph.js';
|
|
18
|
+
/**
|
|
19
|
+
* Conservative per-stack-frame estimate in bytes. A minimal AVR function frame
|
|
20
|
+
* (return address + saved registers + a few locals) is 4-16 bytes; we use the
|
|
21
|
+
* upper bound to avoid underestimating. This is inherently approximate — the
|
|
22
|
+
* goal is to surface a tight budget, not to predict an exact collision.
|
|
23
|
+
*/
|
|
24
|
+
const ESTIMATED_FRAME_BYTES = 16;
|
|
25
|
+
/** Warn when estimated usage exceeds this fraction of SRAM. */
|
|
26
|
+
const WARN_FRACTION = 0.80;
|
|
27
|
+
/**
|
|
28
|
+
* Validate that the estimated memory budget fits within the board's SRAM.
|
|
29
|
+
* Emits a warning when static globals + estimated stack frames exceed 80% of
|
|
30
|
+
* SRAM. Only runs when boardConstants provides `memory.sram` and
|
|
31
|
+
* `architecture`.
|
|
32
|
+
*/
|
|
33
|
+
export function validateMemoryBudget(program, boardConstants) {
|
|
34
|
+
const diagnostics = [];
|
|
35
|
+
if (!boardConstants)
|
|
36
|
+
return diagnostics;
|
|
37
|
+
const architecture = boardConstants.get('architecture');
|
|
38
|
+
if (architecture === undefined)
|
|
39
|
+
return diagnostics;
|
|
40
|
+
// SRAM budget: prefer the board constant, fall back to a per-architecture
|
|
41
|
+
// default. The memory.sram constant may be absent because
|
|
42
|
+
// resolveAndMergeMCUConstants skips MCU resolution when the board file
|
|
43
|
+
// already produced pins.all.* entries (e.g. the Uno spreads ATmega328P.pins).
|
|
44
|
+
// The fallback covers the common AVR boards where this matters most.
|
|
45
|
+
const ARCH_SRAM_DEFAULTS = {
|
|
46
|
+
avr: 2048,
|
|
47
|
+
megaavr: 6144,
|
|
48
|
+
esp32: 327680,
|
|
49
|
+
esp32s2: 327680,
|
|
50
|
+
esp32s3: 327680,
|
|
51
|
+
esp32c3: 327680,
|
|
52
|
+
esp32c6: 327680,
|
|
53
|
+
};
|
|
54
|
+
const sramBytes = boardConstants.get('memory.sram')
|
|
55
|
+
?? ARCH_SRAM_DEFAULTS[architecture];
|
|
56
|
+
if (sramBytes === undefined)
|
|
57
|
+
return diagnostics;
|
|
58
|
+
// Compute the static estimate + stack depth. analyzeHeapUsage needs the call
|
|
59
|
+
// graph for stack-depth estimation.
|
|
60
|
+
const callGraph = buildCallGraph(program);
|
|
61
|
+
const estimate = analyzeHeapUsage(program, architecture, callGraph.nodes);
|
|
62
|
+
// Estimate stack bytes: depth × per-frame. This is a lower bound (each frame
|
|
63
|
+
// may hold locals larger than the default), so frame it conservatively.
|
|
64
|
+
const estimatedStackBytes = estimate.estimatedStackDepth * ESTIMATED_FRAME_BYTES;
|
|
65
|
+
const totalEstimated = estimate.totalStaticBytes + estimatedStackBytes;
|
|
66
|
+
const fraction = totalEstimated / sramBytes;
|
|
67
|
+
if (fraction >= WARN_FRACTION) {
|
|
68
|
+
const sramKb = (sramBytes / 1024).toFixed(1);
|
|
69
|
+
const staticKb = (estimate.totalStaticBytes / 1024).toFixed(2);
|
|
70
|
+
const stackKb = (estimatedStackBytes / 1024).toFixed(2);
|
|
71
|
+
const totalKb = (totalEstimated / 1024).toFixed(2);
|
|
72
|
+
diagnostics.push({
|
|
73
|
+
severity: fraction >= 1.0 ? 'error' : 'warning',
|
|
74
|
+
message: `Estimated SRAM usage is ${(fraction * 100).toFixed(0)}% of the ${sramKb} KB budget ` +
|
|
75
|
+
`(statics ${staticKb} KB + stack ~${stackKb} KB = ~${totalKb} KB). ` +
|
|
76
|
+
(fraction >= 1.0
|
|
77
|
+
? `This exceeds available SRAM and will likely corrupt memory at runtime.`
|
|
78
|
+
: `Stack and heap share the remaining space; deep call chains or heap allocation risk collision.`),
|
|
79
|
+
hint: `Reduce global/static data, move large buffers to PROGMEM (flash), shorten deep call chains, ` +
|
|
80
|
+
`or use a board with more SRAM. See the heap estimate in the diagnostics report for a breakdown.`,
|
|
81
|
+
code: 'memory-budget',
|
|
82
|
+
source: 'memory-budget-validation',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return diagnostics;
|
|
86
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { Diagnostic } from "../types.js";
|
|
3
|
+
import { NamespaceIR } from "../api/index.js";
|
|
4
|
+
import { CppTypeHint } from "./type-resolution.js";
|
|
5
|
+
import { RegisterClassIR } from "../api/index.js";
|
|
6
|
+
import { PointerTracker } from "./build-ir-state.js";
|
|
7
|
+
export declare function namespaceToIR(node: ts.ModuleDeclaration, fileName: string, sourceText: string, diagnostics: Diagnostic[], functionReturnTypes: Map<string, CppTypeHint>, typeAliasNodes: Map<string, ts.TypeNode>, registerClasses: RegisterClassIR[], pointerVars?: PointerTracker): NamespaceIR | undefined;
|