c-next 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -14
- package/package.json +2 -2
- package/src/codegen/CodeGenerator.ts +89 -13
- package/src/codegen/SymbolCollector.ts +190 -0
- package/src/codegen/generators/ISymbolInfo.ts +17 -0
- package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +83 -2
- package/src/codegen/generators/expressions/BinaryExprGenerator.ts +118 -19
- package/src/codegen/generators/support/HelperGenerator.ts +49 -17
- package/src/codegen/types/ICodeGeneratorOptions.ts +5 -0
- package/src/pipeline/Pipeline.ts +5 -0
package/README.md
CHANGED
|
@@ -303,36 +303,38 @@ GPIO7.DR_SET[LED_BIT] <- true; // Generates: GPIO7_DR_SET = (1 << LED_BIT);
|
|
|
303
303
|
|
|
304
304
|
### Slice Assignment for Memory Operations
|
|
305
305
|
|
|
306
|
-
Multi-byte copying with
|
|
306
|
+
Multi-byte copying with compile-time validated `memcpy` generation (Issue #234):
|
|
307
307
|
|
|
308
308
|
```cnx
|
|
309
309
|
u8 buffer[256];
|
|
310
310
|
u32 magic <- 0x12345678;
|
|
311
|
-
u8 length <- 4;
|
|
312
|
-
u32 offset <- 0;
|
|
313
311
|
|
|
314
|
-
// Copy 4 bytes from value into buffer at offset
|
|
315
|
-
buffer[
|
|
312
|
+
// Copy 4 bytes from value into buffer at offset 0
|
|
313
|
+
buffer[0, 4] <- magic;
|
|
314
|
+
|
|
315
|
+
// Named offsets using const variables
|
|
316
|
+
const u32 HEADER_OFFSET <- 0;
|
|
317
|
+
const u32 DATA_OFFSET <- 8;
|
|
318
|
+
buffer[HEADER_OFFSET, 4] <- magic;
|
|
319
|
+
buffer[DATA_OFFSET, 8] <- timestamp;
|
|
316
320
|
```
|
|
317
321
|
|
|
318
|
-
Transpiles to
|
|
322
|
+
Transpiles to direct memcpy (bounds validated at compile time):
|
|
319
323
|
|
|
320
324
|
```c
|
|
321
325
|
uint8_t buffer[256] = {0};
|
|
322
326
|
uint32_t magic = 0x12345678;
|
|
323
|
-
uint8_t length = 4;
|
|
324
|
-
uint32_t offset = 0;
|
|
325
327
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
328
|
+
memcpy(&buffer[0], &magic, 4);
|
|
329
|
+
memcpy(&buffer[8], ×tamp, 8);
|
|
329
330
|
```
|
|
330
331
|
|
|
331
332
|
**Key Features:**
|
|
332
333
|
|
|
333
|
-
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
334
|
+
- **Compile-time bounds checking** prevents buffer overflows at compile time
|
|
335
|
+
- Offset and length must be compile-time constants (literals or `const` variables)
|
|
336
|
+
- Silent runtime failures are now compile-time errors
|
|
337
|
+
- Works with struct fields: `buffer[0, 4] <- config.magic`
|
|
336
338
|
- Distinct from bit operations: array slices use `memcpy`, scalar bit ranges use bit manipulation
|
|
337
339
|
|
|
338
340
|
### Scopes (ADR-016)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"coverage:grammar:console": "tsx scripts/grammar-coverage.ts console",
|
|
38
38
|
"unit": "vitest run",
|
|
39
39
|
"unit:watch": "vitest",
|
|
40
|
-
"test:all": "npm run unit && npm test"
|
|
40
|
+
"test:all": "npm run unit && npm run test:q"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"c",
|
|
@@ -1131,6 +1131,20 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1131
1131
|
output.push(" */");
|
|
1132
1132
|
output.push("");
|
|
1133
1133
|
|
|
1134
|
+
// Issue #230: Self-include for extern "C" linkage
|
|
1135
|
+
// When file has public symbols and headers are being generated,
|
|
1136
|
+
// include own header to ensure proper C linkage
|
|
1137
|
+
if (
|
|
1138
|
+
options?.generateHeaders &&
|
|
1139
|
+
this.symbols!.hasPublicSymbols() &&
|
|
1140
|
+
this.sourcePath
|
|
1141
|
+
) {
|
|
1142
|
+
const basename = this.sourcePath.replace(/^.*[\\/]/, "");
|
|
1143
|
+
const headerName = basename.replace(/\.cnx$|\.cnext$/, ".h");
|
|
1144
|
+
output.push(`#include "${headerName}"`);
|
|
1145
|
+
output.push("");
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1134
1148
|
// Pass through #include directives from source
|
|
1135
1149
|
// C-Next does NOT hardcode any libraries - all includes must be explicit
|
|
1136
1150
|
// ADR-043: Comments before first include become file-level comments
|
|
@@ -5603,9 +5617,43 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5603
5617
|
if (isActualArray || isISRType) {
|
|
5604
5618
|
// Check for slice assignment: array[offset, length] <- value
|
|
5605
5619
|
if (exprs.length === 2) {
|
|
5606
|
-
// Slice assignment -
|
|
5607
|
-
|
|
5608
|
-
const
|
|
5620
|
+
// Issue #234: Slice assignment requires compile-time constant offset and length
|
|
5621
|
+
// to ensure bounds safety at compile time, not runtime
|
|
5622
|
+
const offsetValue = this._tryEvaluateConstant(exprs[0]);
|
|
5623
|
+
const lengthValue = this._tryEvaluateConstant(exprs[1]);
|
|
5624
|
+
|
|
5625
|
+
const line = exprs[0].start?.line ?? arrayAccessCtx.start?.line ?? 0;
|
|
5626
|
+
|
|
5627
|
+
// Issue #234: Reject slice assignment on multi-dimensional arrays
|
|
5628
|
+
// Slice assignment is only valid on 1D arrays (the innermost dimension)
|
|
5629
|
+
// For multi-dimensional arrays like board[4][8], use board[row][offset, length]
|
|
5630
|
+
// (Note: grammar currently doesn't support this - tracked as future work)
|
|
5631
|
+
if (
|
|
5632
|
+
typeInfo?.arrayDimensions &&
|
|
5633
|
+
typeInfo.arrayDimensions.length > 1
|
|
5634
|
+
) {
|
|
5635
|
+
throw new Error(
|
|
5636
|
+
`${line}:0 Error: Slice assignment is only valid on one-dimensional arrays. ` +
|
|
5637
|
+
`'${name}' has ${typeInfo.arrayDimensions.length} dimensions. ` +
|
|
5638
|
+
`Access the innermost dimension first (e.g., ${name}[index][offset, length]).`,
|
|
5639
|
+
);
|
|
5640
|
+
}
|
|
5641
|
+
|
|
5642
|
+
// Validate offset is compile-time constant
|
|
5643
|
+
if (offsetValue === undefined) {
|
|
5644
|
+
throw new Error(
|
|
5645
|
+
`${line}:0 Error: Slice assignment offset must be a compile-time constant. ` +
|
|
5646
|
+
`Runtime offsets are not allowed to ensure bounds safety.`,
|
|
5647
|
+
);
|
|
5648
|
+
}
|
|
5649
|
+
|
|
5650
|
+
// Validate length is compile-time constant
|
|
5651
|
+
if (lengthValue === undefined) {
|
|
5652
|
+
throw new Error(
|
|
5653
|
+
`${line}:0 Error: Slice assignment length must be a compile-time constant. ` +
|
|
5654
|
+
`Runtime lengths are not allowed to ensure bounds safety.`,
|
|
5655
|
+
);
|
|
5656
|
+
}
|
|
5609
5657
|
|
|
5610
5658
|
// Compound operators not supported for slice assignment
|
|
5611
5659
|
if (cOp !== "=") {
|
|
@@ -5614,22 +5662,50 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5614
5662
|
);
|
|
5615
5663
|
}
|
|
5616
5664
|
|
|
5617
|
-
//
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
// Generate bounds-checked memcpy
|
|
5621
|
-
// if (offset + length <= sizeof(buffer)) { memcpy(&buffer[offset], &value, length); }
|
|
5622
|
-
// Issue #213: For string parameters, use capacity for bounds checking
|
|
5623
|
-
// since sizeof(char*) gives pointer size, not buffer size
|
|
5665
|
+
// Determine buffer capacity for compile-time bounds check
|
|
5666
|
+
let capacity: number;
|
|
5624
5667
|
if (
|
|
5625
5668
|
typeInfo?.isString &&
|
|
5626
5669
|
typeInfo.stringCapacity &&
|
|
5627
5670
|
!typeInfo.isArray
|
|
5628
5671
|
) {
|
|
5629
|
-
|
|
5630
|
-
|
|
5672
|
+
capacity = typeInfo.stringCapacity + 1;
|
|
5673
|
+
} else if (typeInfo?.arrayDimensions && typeInfo.arrayDimensions[0]) {
|
|
5674
|
+
capacity = typeInfo.arrayDimensions[0];
|
|
5675
|
+
} else {
|
|
5676
|
+
// Can't determine capacity at compile time - this shouldn't happen
|
|
5677
|
+
// for properly tracked arrays, but fall back to error
|
|
5678
|
+
throw new Error(
|
|
5679
|
+
`${line}:0 Error: Cannot determine buffer size for '${name}' at compile time.`,
|
|
5680
|
+
);
|
|
5631
5681
|
}
|
|
5632
|
-
|
|
5682
|
+
|
|
5683
|
+
// Issue #234: Compile-time bounds validation
|
|
5684
|
+
if (offsetValue + lengthValue > capacity) {
|
|
5685
|
+
throw new Error(
|
|
5686
|
+
`${line}:0 Error: Slice assignment out of bounds: ` +
|
|
5687
|
+
`offset(${offsetValue}) + length(${lengthValue}) = ${offsetValue + lengthValue} ` +
|
|
5688
|
+
`exceeds buffer capacity(${capacity}) for '${name}'.`,
|
|
5689
|
+
);
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5692
|
+
if (offsetValue < 0) {
|
|
5693
|
+
throw new Error(
|
|
5694
|
+
`${line}:0 Error: Slice assignment offset cannot be negative: ${offsetValue}`,
|
|
5695
|
+
);
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
if (lengthValue <= 0) {
|
|
5699
|
+
throw new Error(
|
|
5700
|
+
`${line}:0 Error: Slice assignment length must be positive: ${lengthValue}`,
|
|
5701
|
+
);
|
|
5702
|
+
}
|
|
5703
|
+
|
|
5704
|
+
// Set flag to include string.h for memcpy
|
|
5705
|
+
this.needsString = true;
|
|
5706
|
+
|
|
5707
|
+
// Generate memcpy without runtime bounds check (already validated at compile time)
|
|
5708
|
+
return `memcpy(&${name}[${offsetValue}], &${value}, ${lengthValue});`;
|
|
5633
5709
|
}
|
|
5634
5710
|
|
|
5635
5711
|
// Normal array element assignment (single index)
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Issue #60: Extracted from CodeGenerator for better separation of concerns.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { ParserRuleContext } from "antlr4ng";
|
|
8
9
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
9
10
|
import BITMAP_SIZE from "./types/BITMAP_SIZE";
|
|
10
11
|
import BITMAP_BACKING_TYPE from "./types/BITMAP_BACKING_TYPE";
|
|
@@ -58,6 +59,16 @@ class SymbolCollector {
|
|
|
58
59
|
// For scope context during collection (used by getTypeName)
|
|
59
60
|
private _collectingScope: string | null = null;
|
|
60
61
|
|
|
62
|
+
// Issue #232: Track which scope variables are used in which functions
|
|
63
|
+
// Maps "ScopeName_varName" -> Set of function names that use it
|
|
64
|
+
private _scopeVariableUsage: Map<string, Set<string>> = new Map();
|
|
65
|
+
|
|
66
|
+
// Store function declarations for usage analysis
|
|
67
|
+
private _scopeFunctionBodies: Map<
|
|
68
|
+
string,
|
|
69
|
+
Parser.FunctionDeclarationContext[]
|
|
70
|
+
> = new Map();
|
|
71
|
+
|
|
61
72
|
// Readonly public accessors
|
|
62
73
|
get knownScopes(): ReadonlySet<string> {
|
|
63
74
|
return this._knownScopes;
|
|
@@ -143,6 +154,30 @@ class SymbolCollector {
|
|
|
143
154
|
return this._registerMemberCTypes;
|
|
144
155
|
}
|
|
145
156
|
|
|
157
|
+
// Issue #232: Getter for scope variable usage analysis
|
|
158
|
+
get scopeVariableUsage(): ReadonlyMap<string, Set<string>> {
|
|
159
|
+
return this._scopeVariableUsage;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Issue #232: Check if a scope variable is used in only one function.
|
|
164
|
+
* Returns the function name if single-function, null otherwise.
|
|
165
|
+
*/
|
|
166
|
+
getSingleFunctionForVariable(
|
|
167
|
+
scopeName: string,
|
|
168
|
+
varName: string,
|
|
169
|
+
): string | null {
|
|
170
|
+
const fullVarName = `${scopeName}_${varName}`;
|
|
171
|
+
const usedIn = this._scopeVariableUsage.get(fullVarName);
|
|
172
|
+
|
|
173
|
+
if (!usedIn || usedIn.size !== 1) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Extract the single element from the Set (we know it exists since size === 1)
|
|
178
|
+
return [...usedIn][0];
|
|
179
|
+
}
|
|
180
|
+
|
|
146
181
|
/**
|
|
147
182
|
* Create a new SymbolCollector and collect all symbols from the parse tree.
|
|
148
183
|
* @param tree The C-Next program parse tree
|
|
@@ -259,6 +294,12 @@ class SymbolCollector {
|
|
|
259
294
|
const funcName = funcDecl.IDENTIFIER().getText();
|
|
260
295
|
members.add(funcName);
|
|
261
296
|
memberVisibility.set(funcName, visibility);
|
|
297
|
+
|
|
298
|
+
// Issue #232: Store function for usage analysis
|
|
299
|
+
if (!this._scopeFunctionBodies.has(name)) {
|
|
300
|
+
this._scopeFunctionBodies.set(name, []);
|
|
301
|
+
}
|
|
302
|
+
this._scopeFunctionBodies.get(name)!.push(funcDecl);
|
|
262
303
|
}
|
|
263
304
|
|
|
264
305
|
// ADR-017: Collect enums declared inside scopes
|
|
@@ -322,10 +363,144 @@ class SymbolCollector {
|
|
|
322
363
|
this._scopeMembers.set(name, members);
|
|
323
364
|
this._scopeMemberVisibility.set(name, memberVisibility);
|
|
324
365
|
|
|
366
|
+
// Issue #232: Analyze which functions use which scope variables
|
|
367
|
+
this.analyzeScopeVariableUsage(name, members);
|
|
368
|
+
|
|
325
369
|
// Clear collecting scope
|
|
326
370
|
this._collectingScope = null;
|
|
327
371
|
}
|
|
328
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Issue #232: Analyze which scope variables are used in which functions.
|
|
375
|
+
* Issue #233: Also track which variables are WRITTEN to (for reset injection).
|
|
376
|
+
* This enables making single-function variables local instead of file-scope.
|
|
377
|
+
*/
|
|
378
|
+
private analyzeScopeVariableUsage(
|
|
379
|
+
scopeName: string,
|
|
380
|
+
members: Set<string>,
|
|
381
|
+
): void {
|
|
382
|
+
const functions = this._scopeFunctionBodies.get(scopeName);
|
|
383
|
+
if (!functions) return;
|
|
384
|
+
|
|
385
|
+
// Collect all variable names (non-function members)
|
|
386
|
+
const variableNames = new Set<string>();
|
|
387
|
+
for (const member of members) {
|
|
388
|
+
// Check if this is a function by seeing if we have it in the function list
|
|
389
|
+
const isFuncMember = functions.some(
|
|
390
|
+
(f) => f.IDENTIFIER().getText() === member,
|
|
391
|
+
);
|
|
392
|
+
if (!isFuncMember) {
|
|
393
|
+
variableNames.add(member);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// For each function, find which variables it references and writes
|
|
398
|
+
for (const funcDecl of functions) {
|
|
399
|
+
const funcName = funcDecl.IDENTIFIER().getText();
|
|
400
|
+
const block = funcDecl.block();
|
|
401
|
+
if (!block) continue;
|
|
402
|
+
|
|
403
|
+
// Find all this.varName references in the function body (reads + writes)
|
|
404
|
+
const usedVars = this.findScopedMemberReferences(block, variableNames);
|
|
405
|
+
|
|
406
|
+
// Record usage
|
|
407
|
+
for (const varName of usedVars) {
|
|
408
|
+
const fullVarName = `${scopeName}_${varName}`;
|
|
409
|
+
if (!this._scopeVariableUsage.has(fullVarName)) {
|
|
410
|
+
this._scopeVariableUsage.set(fullVarName, new Set());
|
|
411
|
+
}
|
|
412
|
+
this._scopeVariableUsage.get(fullVarName)!.add(funcName);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Issue #232: Recursively find all this.varName references in an AST node.
|
|
419
|
+
* Returns a set of variable names that are referenced.
|
|
420
|
+
*
|
|
421
|
+
* `this.temp` parses as PostfixExpressionContext with:
|
|
422
|
+
* - primaryExpression: 'this'
|
|
423
|
+
* - postfixOp[0]: '.temp'
|
|
424
|
+
*/
|
|
425
|
+
private findScopedMemberReferences(
|
|
426
|
+
node: ParserRuleContext,
|
|
427
|
+
variableNames: Set<string>,
|
|
428
|
+
): Set<string> {
|
|
429
|
+
const found = new Set<string>();
|
|
430
|
+
|
|
431
|
+
// Check if this node is a PostfixExpression with 'this' primary
|
|
432
|
+
if (node instanceof Parser.PostfixExpressionContext) {
|
|
433
|
+
const primary = node.primaryExpression();
|
|
434
|
+
if (primary && primary.getText() === "this") {
|
|
435
|
+
// Get the first postfixOp - it should be .memberName
|
|
436
|
+
const ops = node.postfixOp();
|
|
437
|
+
if (ops.length > 0) {
|
|
438
|
+
const firstOp = ops[0];
|
|
439
|
+
const identifier = firstOp.IDENTIFIER();
|
|
440
|
+
if (identifier) {
|
|
441
|
+
const memberName = identifier.getText();
|
|
442
|
+
if (variableNames.has(memberName)) {
|
|
443
|
+
found.add(memberName);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Also check thisAccess, thisMemberAccess, thisArrayAccess in lvalue context
|
|
451
|
+
if (node instanceof Parser.ThisAccessContext) {
|
|
452
|
+
const identifier = node.IDENTIFIER();
|
|
453
|
+
if (identifier) {
|
|
454
|
+
const memberName = identifier.getText();
|
|
455
|
+
if (variableNames.has(memberName)) {
|
|
456
|
+
found.add(memberName);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (node instanceof Parser.ThisMemberAccessContext) {
|
|
462
|
+
// First IDENTIFIER after 'this.'
|
|
463
|
+
const identifiers = node.IDENTIFIER();
|
|
464
|
+
if (identifiers.length > 0) {
|
|
465
|
+
const firstMember = identifiers[0].getText();
|
|
466
|
+
if (variableNames.has(firstMember)) {
|
|
467
|
+
found.add(firstMember);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (node instanceof Parser.ThisArrayAccessContext) {
|
|
473
|
+
// First IDENTIFIER after 'this.'
|
|
474
|
+
const identifiers = node.IDENTIFIER();
|
|
475
|
+
if (identifiers.length > 0) {
|
|
476
|
+
const firstMember = identifiers[0].getText();
|
|
477
|
+
if (variableNames.has(firstMember)) {
|
|
478
|
+
found.add(firstMember);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Recursively check all children using .children property.
|
|
484
|
+
// Type assertion needed because antlr4ng's ParserRuleContext doesn't expose
|
|
485
|
+
// .children in its public type definitions, but it exists at runtime.
|
|
486
|
+
const children = (node as { children?: unknown[] }).children;
|
|
487
|
+
if (children) {
|
|
488
|
+
for (const child of children) {
|
|
489
|
+
if (child instanceof ParserRuleContext) {
|
|
490
|
+
const childFound = this.findScopedMemberReferences(
|
|
491
|
+
child,
|
|
492
|
+
variableNames,
|
|
493
|
+
);
|
|
494
|
+
for (const v of childFound) {
|
|
495
|
+
found.add(v);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return found;
|
|
502
|
+
}
|
|
503
|
+
|
|
329
504
|
/**
|
|
330
505
|
* Collect struct declarations and track field types
|
|
331
506
|
*/
|
|
@@ -539,6 +714,21 @@ class SymbolCollector {
|
|
|
539
714
|
}
|
|
540
715
|
return value;
|
|
541
716
|
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Issue #230: Check if any scope members are public (exported)
|
|
720
|
+
* Used to determine if a self-include header is needed for extern "C" linkage
|
|
721
|
+
*/
|
|
722
|
+
hasPublicSymbols(): boolean {
|
|
723
|
+
for (const [, visibilityMap] of this._scopeMemberVisibility) {
|
|
724
|
+
for (const [, visibility] of visibilityMap) {
|
|
725
|
+
if (visibility === "public") {
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
542
732
|
}
|
|
543
733
|
|
|
544
734
|
export default SymbolCollector;
|
|
@@ -91,6 +91,23 @@ interface ISymbolInfo {
|
|
|
91
91
|
|
|
92
92
|
/** Register member C types: "reg_member" -> C type (e.g., "uint32_t") */
|
|
93
93
|
readonly registerMemberCTypes: ReadonlyMap<string, string>;
|
|
94
|
+
|
|
95
|
+
// === Issue #232: Scope Variable Usage Analysis ===
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Scope variable usage: "Scope_varName" -> Set of function names that use it.
|
|
99
|
+
* Used to determine if a variable is single-function (can be local).
|
|
100
|
+
*/
|
|
101
|
+
readonly scopeVariableUsage: ReadonlyMap<string, ReadonlySet<string>>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if a scope variable is used in only one function.
|
|
105
|
+
* Returns the function name if single-function, null otherwise.
|
|
106
|
+
*/
|
|
107
|
+
getSingleFunctionForVariable(
|
|
108
|
+
scopeName: string,
|
|
109
|
+
varName: string,
|
|
110
|
+
): string | null;
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
export default ISymbolInfo;
|
|
@@ -30,6 +30,15 @@ import generateScopedRegister from "./ScopedRegisterGenerator";
|
|
|
30
30
|
* - Visibility control (private -> static, public -> extern)
|
|
31
31
|
* - Organization without runtime overhead
|
|
32
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* Issue #232: Information about a single-function variable
|
|
35
|
+
*/
|
|
36
|
+
interface ISingleFunctionVar {
|
|
37
|
+
varDecl: Parser.VariableDeclarationContext;
|
|
38
|
+
targetFunction: string;
|
|
39
|
+
isPrivate: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
34
43
|
node: Parser.ScopeDeclarationContext,
|
|
35
44
|
input: IGeneratorInput,
|
|
@@ -44,6 +53,10 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
44
53
|
const lines: string[] = [];
|
|
45
54
|
lines.push(`/* Scope: ${name} */`);
|
|
46
55
|
|
|
56
|
+
// Issue #232: First pass - identify single-function variables
|
|
57
|
+
// Maps functionName -> list of variable declarations that are local to it
|
|
58
|
+
const localVarsForFunction = new Map<string, ISingleFunctionVar[]>();
|
|
59
|
+
|
|
47
60
|
for (const member of node.scopeMember()) {
|
|
48
61
|
const visibility = member.visibilityModifier()?.getText() || "private";
|
|
49
62
|
const isPrivate = visibility === "private";
|
|
@@ -51,8 +64,30 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
51
64
|
// Handle variable declarations
|
|
52
65
|
if (member.variableDeclaration()) {
|
|
53
66
|
const varDecl = member.variableDeclaration()!;
|
|
54
|
-
const type = orchestrator.generateType(varDecl.type());
|
|
55
67
|
const varName = varDecl.IDENTIFIER().getText();
|
|
68
|
+
|
|
69
|
+
// Issue #232: Check if this PRIVATE variable is used in only one function
|
|
70
|
+
// PUBLIC variables must stay file-scope because they can be accessed from outside
|
|
71
|
+
const targetFunc = isPrivate
|
|
72
|
+
? input.symbols?.getSingleFunctionForVariable(name, varName)
|
|
73
|
+
: null;
|
|
74
|
+
|
|
75
|
+
if (targetFunc) {
|
|
76
|
+
// Single-function private variable - emit as local, not file-scope
|
|
77
|
+
if (!localVarsForFunction.has(targetFunc)) {
|
|
78
|
+
localVarsForFunction.set(targetFunc, []);
|
|
79
|
+
}
|
|
80
|
+
localVarsForFunction.get(targetFunc)!.push({
|
|
81
|
+
varDecl,
|
|
82
|
+
targetFunction: targetFunc,
|
|
83
|
+
isPrivate,
|
|
84
|
+
});
|
|
85
|
+
// Don't emit file-scope declaration
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Multi-function or unused variable - emit at file scope (original behavior)
|
|
90
|
+
const type = orchestrator.generateType(varDecl.type());
|
|
56
91
|
const fullName = `${name}_${varName}`;
|
|
57
92
|
const prefix = isPrivate ? "static " : "";
|
|
58
93
|
|
|
@@ -99,7 +134,53 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
99
134
|
? orchestrator.generateParameterList(funcDecl.parameterList()!)
|
|
100
135
|
: "void";
|
|
101
136
|
|
|
102
|
-
|
|
137
|
+
let body = orchestrator.generateBlock(funcDecl.block());
|
|
138
|
+
|
|
139
|
+
// Issue #232: Inject local variable declarations for single-function vars
|
|
140
|
+
const localVars = localVarsForFunction.get(funcName);
|
|
141
|
+
if (localVars && localVars.length > 0) {
|
|
142
|
+
// Generate local variable declarations
|
|
143
|
+
const localDecls: string[] = [];
|
|
144
|
+
for (const { varDecl } of localVars) {
|
|
145
|
+
const varType = orchestrator.generateType(varDecl.type());
|
|
146
|
+
const varName = varDecl.IDENTIFIER().getText();
|
|
147
|
+
const varFullName = `${name}_${varName}`;
|
|
148
|
+
|
|
149
|
+
const arrayDims = varDecl.arrayDimension();
|
|
150
|
+
const isArray = arrayDims.length > 0;
|
|
151
|
+
let decl = ` ${varType} ${varFullName}`;
|
|
152
|
+
if (isArray) {
|
|
153
|
+
decl += orchestrator.generateArrayDimensions(arrayDims);
|
|
154
|
+
}
|
|
155
|
+
// ADR-045: Add string capacity dimension for string arrays
|
|
156
|
+
if (varDecl.type().stringType()) {
|
|
157
|
+
const stringCtx = varDecl.type().stringType()!;
|
|
158
|
+
const intLiteral = stringCtx.INTEGER_LITERAL();
|
|
159
|
+
if (intLiteral) {
|
|
160
|
+
const capacity = parseInt(intLiteral.getText(), 10);
|
|
161
|
+
decl += `[${capacity + 1}]`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (varDecl.expression()) {
|
|
165
|
+
decl += ` = ${orchestrator.generateExpression(varDecl.expression()!)}`;
|
|
166
|
+
} else {
|
|
167
|
+
decl += ` = ${orchestrator.getZeroInitializer(varDecl.type(), isArray)}`;
|
|
168
|
+
}
|
|
169
|
+
localDecls.push(decl + ";");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Inject after opening brace: { -> {\n local_var = init;
|
|
173
|
+
// Body format is typically "{\n ...statements...\n}"
|
|
174
|
+
const openBraceIdx = body.indexOf("{");
|
|
175
|
+
if (openBraceIdx !== -1) {
|
|
176
|
+
const afterBrace = body.substring(openBraceIdx + 1);
|
|
177
|
+
body =
|
|
178
|
+
body.substring(0, openBraceIdx + 1) +
|
|
179
|
+
"\n" +
|
|
180
|
+
localDecls.join("\n") +
|
|
181
|
+
afterBrace;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
103
184
|
|
|
104
185
|
// ADR-016: Exit function body context
|
|
105
186
|
orchestrator.exitFunctionBody();
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* - Bitwise: |, ^, &
|
|
9
9
|
* - Shift: <<, >> with validation
|
|
10
10
|
* - Arithmetic: +, -, *, /, %
|
|
11
|
+
*
|
|
12
|
+
* Issue #235: Includes constant folding for compile-time constant expressions.
|
|
11
13
|
*/
|
|
12
14
|
import * as Parser from "../../../parser/grammar/CNextParser";
|
|
13
15
|
import IGeneratorOutput from "../IGeneratorOutput";
|
|
@@ -16,6 +18,89 @@ import IGeneratorInput from "../IGeneratorInput";
|
|
|
16
18
|
import IGeneratorState from "../IGeneratorState";
|
|
17
19
|
import IOrchestrator from "../IOrchestrator";
|
|
18
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Issue #235: Try to parse a string as a numeric constant.
|
|
23
|
+
* Returns the numeric value if it's a simple integer literal, undefined otherwise.
|
|
24
|
+
* Handles decimal, hex (0x), and binary (0b) formats.
|
|
25
|
+
*/
|
|
26
|
+
const tryParseNumericLiteral = (code: string): number | undefined => {
|
|
27
|
+
const trimmed = code.trim();
|
|
28
|
+
|
|
29
|
+
// Decimal integer (including negative)
|
|
30
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
31
|
+
return parseInt(trimmed, 10);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Hex literal
|
|
35
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(trimmed)) {
|
|
36
|
+
return parseInt(trimmed, 16);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Binary literal
|
|
40
|
+
if (/^0[bB][01]+$/.test(trimmed)) {
|
|
41
|
+
return parseInt(trimmed.substring(2), 2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Issue #235: Evaluate a constant arithmetic expression.
|
|
49
|
+
* Returns the result if all operands are numeric and evaluation succeeds,
|
|
50
|
+
* undefined otherwise (falls back to non-folded code).
|
|
51
|
+
*/
|
|
52
|
+
const tryFoldConstants = (
|
|
53
|
+
operandCodes: string[],
|
|
54
|
+
operators: string[],
|
|
55
|
+
): number | undefined => {
|
|
56
|
+
// Parse all operands as numbers
|
|
57
|
+
const values = operandCodes.map(tryParseNumericLiteral);
|
|
58
|
+
|
|
59
|
+
// If any operand is not a constant, we can't fold
|
|
60
|
+
if (values.some((v) => v === undefined)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Evaluate left-to-right with the operators
|
|
65
|
+
let result = values[0] as number;
|
|
66
|
+
for (let i = 0; i < operators.length; i++) {
|
|
67
|
+
const op = operators[i];
|
|
68
|
+
const rightValue = values[i + 1] as number;
|
|
69
|
+
|
|
70
|
+
switch (op) {
|
|
71
|
+
case "*":
|
|
72
|
+
result = result * rightValue;
|
|
73
|
+
break;
|
|
74
|
+
case "/":
|
|
75
|
+
// Avoid division by zero - let C compiler handle it
|
|
76
|
+
if (rightValue === 0) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
// Integer division (truncate toward zero like C)
|
|
80
|
+
result = Math.trunc(result / rightValue);
|
|
81
|
+
break;
|
|
82
|
+
case "%":
|
|
83
|
+
// Avoid modulo by zero
|
|
84
|
+
if (rightValue === 0) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
result = result % rightValue;
|
|
88
|
+
break;
|
|
89
|
+
case "+":
|
|
90
|
+
result = result + rightValue;
|
|
91
|
+
break;
|
|
92
|
+
case "-":
|
|
93
|
+
result = result - rightValue;
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
// Unknown operator - don't fold
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
|
|
19
104
|
/**
|
|
20
105
|
* Generate C code for an OR expression (lowest precedence binary op).
|
|
21
106
|
*/
|
|
@@ -320,6 +405,7 @@ const generateShiftExpr = (
|
|
|
320
405
|
|
|
321
406
|
/**
|
|
322
407
|
* Generate C code for an additive expression.
|
|
408
|
+
* Issue #235: Includes constant folding for compile-time constant expressions.
|
|
323
409
|
*/
|
|
324
410
|
const generateAdditiveExpr = (
|
|
325
411
|
node: Parser.AdditiveExpressionContext,
|
|
@@ -336,25 +422,25 @@ const generateAdditiveExpr = (
|
|
|
336
422
|
|
|
337
423
|
// Issue #152: Extract operators in order from parse tree children
|
|
338
424
|
const operators = orchestrator.getOperatorsFromChildren(node);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
state,
|
|
343
|
-
orchestrator,
|
|
425
|
+
|
|
426
|
+
// Generate code for all operands
|
|
427
|
+
const operandResults = exprs.map((expr) =>
|
|
428
|
+
generateMultiplicativeExpr(expr, input, state, orchestrator),
|
|
344
429
|
);
|
|
345
|
-
|
|
346
|
-
|
|
430
|
+
const operandCodes = operandResults.map((r) => r.code);
|
|
431
|
+
operandResults.forEach((r) => effects.push(...r.effects));
|
|
347
432
|
|
|
348
|
-
|
|
433
|
+
// Issue #235: Try constant folding for compile-time constant expressions
|
|
434
|
+
const foldedResult = tryFoldConstants(operandCodes, operators);
|
|
435
|
+
if (foldedResult !== undefined) {
|
|
436
|
+
return { code: String(foldedResult), effects };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Fall back to standard code generation
|
|
440
|
+
let result = operandCodes[0];
|
|
441
|
+
for (let i = 1; i < operandCodes.length; i++) {
|
|
349
442
|
const op = operators[i - 1] || "+";
|
|
350
|
-
|
|
351
|
-
exprs[i],
|
|
352
|
-
input,
|
|
353
|
-
state,
|
|
354
|
-
orchestrator,
|
|
355
|
-
);
|
|
356
|
-
effects.push(...exprResult.effects);
|
|
357
|
-
result += ` ${op} ${exprResult.code}`;
|
|
443
|
+
result += ` ${op} ${operandCodes[i]}`;
|
|
358
444
|
}
|
|
359
445
|
|
|
360
446
|
return { code: result, effects };
|
|
@@ -363,6 +449,7 @@ const generateAdditiveExpr = (
|
|
|
363
449
|
/**
|
|
364
450
|
* Generate C code for a multiplicative expression.
|
|
365
451
|
* This is the bottom of the binary chain - delegates to unary via orchestrator.
|
|
452
|
+
* Issue #235: Includes constant folding for compile-time constant expressions.
|
|
366
453
|
*/
|
|
367
454
|
const generateMultiplicativeExpr = (
|
|
368
455
|
node: Parser.MultiplicativeExpressionContext,
|
|
@@ -380,11 +467,23 @@ const generateMultiplicativeExpr = (
|
|
|
380
467
|
|
|
381
468
|
// Issue #152: Extract operators in order from parse tree children
|
|
382
469
|
const operators = orchestrator.getOperatorsFromChildren(node);
|
|
383
|
-
let result = orchestrator.generateUnaryExpr(exprs[0]);
|
|
384
470
|
|
|
385
|
-
|
|
471
|
+
// Generate code for all operands
|
|
472
|
+
const operandCodes = exprs.map((expr) =>
|
|
473
|
+
orchestrator.generateUnaryExpr(expr),
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Issue #235: Try constant folding for compile-time constant expressions
|
|
477
|
+
const foldedResult = tryFoldConstants(operandCodes, operators);
|
|
478
|
+
if (foldedResult !== undefined) {
|
|
479
|
+
return { code: String(foldedResult), effects: [] };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Fall back to standard code generation
|
|
483
|
+
let result = operandCodes[0];
|
|
484
|
+
for (let i = 1; i < operandCodes.length; i++) {
|
|
386
485
|
const op = operators[i - 1] || "*";
|
|
387
|
-
result += ` ${op} ${
|
|
486
|
+
result += ` ${op} ${operandCodes[i]}`;
|
|
388
487
|
}
|
|
389
488
|
|
|
390
489
|
return { code: result, effects: [] };
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helper function generators for overflow-safe arithmetic and safe division.
|
|
3
3
|
* Extracted from CodeGenerator.ts as part of ADR-053 A5.
|
|
4
|
+
*
|
|
5
|
+
* Portability note: Uses __builtin_add_overflow, __builtin_sub_overflow, and
|
|
6
|
+
* __builtin_mul_overflow intrinsics (GCC 5+, Clang 3.4+). C-Next targets embedded
|
|
7
|
+
* systems using arm-none-eabi-gcc, so these are available. MSVC is not supported.
|
|
4
8
|
*/
|
|
5
9
|
import TYPE_MAP from "../../types/TYPE_MAP";
|
|
6
10
|
import WIDER_TYPE_MAP from "../../types/WIDER_TYPE_MAP";
|
|
@@ -33,10 +37,13 @@ function generateSingleHelper(
|
|
|
33
37
|
switch (operation) {
|
|
34
38
|
case "add":
|
|
35
39
|
if (isUnsigned) {
|
|
36
|
-
//
|
|
40
|
+
// Issue #231: Hybrid approach - check wide operand first, then use builtin
|
|
41
|
+
// Issue #94: Wide operand check prevents truncation issues
|
|
37
42
|
return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${widerType} b) {
|
|
38
|
-
if (b > ${maxValue} - a) return ${maxValue};
|
|
39
|
-
|
|
43
|
+
if (b > (${widerType})(${maxValue} - a)) return ${maxValue};
|
|
44
|
+
${cType} result;
|
|
45
|
+
if (__builtin_add_overflow(a, (${cType})b, &result)) return ${maxValue};
|
|
46
|
+
return result;
|
|
40
47
|
}`;
|
|
41
48
|
} else if (useWiderArithmetic) {
|
|
42
49
|
// Signed addition: compute in wider type, then clamp (Issue #94)
|
|
@@ -58,11 +65,14 @@ function generateSingleHelper(
|
|
|
58
65
|
|
|
59
66
|
case "sub":
|
|
60
67
|
if (isUnsigned) {
|
|
61
|
-
//
|
|
62
|
-
//
|
|
68
|
+
// Issue #231: Hybrid approach - check wide operand first, then use builtin
|
|
69
|
+
// Issue #94: Wide operand check prevents truncation issues
|
|
70
|
+
// Use > (not >=) since b == a produces valid result 0 via the builtin path
|
|
63
71
|
return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${widerType} b) {
|
|
64
|
-
if (b
|
|
65
|
-
|
|
72
|
+
if (b > (${widerType})a) return 0;
|
|
73
|
+
${cType} result;
|
|
74
|
+
if (__builtin_sub_overflow(a, (${cType})b, &result)) return 0;
|
|
75
|
+
return result;
|
|
66
76
|
}`;
|
|
67
77
|
} else if (useWiderArithmetic) {
|
|
68
78
|
// Signed subtraction: compute in wider type, then clamp (Issue #94)
|
|
@@ -83,10 +93,13 @@ function generateSingleHelper(
|
|
|
83
93
|
|
|
84
94
|
case "mul":
|
|
85
95
|
if (isUnsigned) {
|
|
86
|
-
//
|
|
96
|
+
// Issue #231: Hybrid approach - check wide operand first, then use builtin
|
|
97
|
+
// Issue #94: Wide operand check prevents truncation issues
|
|
87
98
|
return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${widerType} b) {
|
|
88
99
|
if (b != 0 && a > ${maxValue} / b) return ${maxValue};
|
|
89
|
-
|
|
100
|
+
${cType} result;
|
|
101
|
+
if (__builtin_mul_overflow(a, (${cType})b, &result)) return ${maxValue};
|
|
102
|
+
return result;
|
|
90
103
|
}`;
|
|
91
104
|
} else if (useWiderArithmetic) {
|
|
92
105
|
// Signed multiplication: compute in wider type, then clamp (Issue #94)
|
|
@@ -144,13 +157,19 @@ function generateDebugHelper(
|
|
|
144
157
|
switch (operation) {
|
|
145
158
|
case "add":
|
|
146
159
|
if (isUnsigned) {
|
|
147
|
-
//
|
|
160
|
+
// Issue #231: Hybrid approach - check wide operand first, then use builtin
|
|
161
|
+
// Issue #94: Wide operand check prevents truncation issues
|
|
148
162
|
return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${widerType} b) {
|
|
149
|
-
if (b > ${maxValue} - a) {
|
|
163
|
+
if (b > (${widerType})(${maxValue} - a)) {
|
|
150
164
|
fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
|
|
151
165
|
abort();
|
|
152
166
|
}
|
|
153
|
-
|
|
167
|
+
${cType} result;
|
|
168
|
+
if (__builtin_add_overflow(a, (${cType})b, &result)) {
|
|
169
|
+
fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
|
|
170
|
+
abort();
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
154
173
|
}`;
|
|
155
174
|
} else if (useWiderArithmetic) {
|
|
156
175
|
// Signed addition: compute in wider type, check bounds (Issue #94)
|
|
@@ -175,13 +194,20 @@ function generateDebugHelper(
|
|
|
175
194
|
|
|
176
195
|
case "sub":
|
|
177
196
|
if (isUnsigned) {
|
|
178
|
-
//
|
|
197
|
+
// Issue #231: Hybrid approach - check wide operand first, then use builtin
|
|
198
|
+
// Issue #94: Wide operand check prevents truncation issues
|
|
199
|
+
// Use > (not >=) since b == a produces valid result 0 via the builtin path
|
|
179
200
|
return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${widerType} b) {
|
|
180
|
-
if (b
|
|
201
|
+
if (b > (${widerType})a) {
|
|
181
202
|
fprintf(stderr, "PANIC: Integer underflow in ${cnxType} ${opName}\\n");
|
|
182
203
|
abort();
|
|
183
204
|
}
|
|
184
|
-
|
|
205
|
+
${cType} result;
|
|
206
|
+
if (__builtin_sub_overflow(a, (${cType})b, &result)) {
|
|
207
|
+
fprintf(stderr, "PANIC: Integer underflow in ${cnxType} ${opName}\\n");
|
|
208
|
+
abort();
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
185
211
|
}`;
|
|
186
212
|
} else if (useWiderArithmetic) {
|
|
187
213
|
// Signed subtraction: compute in wider type, check bounds (Issue #94)
|
|
@@ -206,13 +232,19 @@ function generateDebugHelper(
|
|
|
206
232
|
|
|
207
233
|
case "mul":
|
|
208
234
|
if (isUnsigned) {
|
|
209
|
-
//
|
|
235
|
+
// Issue #231: Hybrid approach - check wide operand first, then use builtin
|
|
236
|
+
// Issue #94: Wide operand check prevents truncation issues
|
|
210
237
|
return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${widerType} b) {
|
|
211
238
|
if (b != 0 && a > ${maxValue} / b) {
|
|
212
239
|
fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
|
|
213
240
|
abort();
|
|
214
241
|
}
|
|
215
|
-
|
|
242
|
+
${cType} result;
|
|
243
|
+
if (__builtin_mul_overflow(a, (${cType})b, &result)) {
|
|
244
|
+
fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
|
|
245
|
+
abort();
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
216
248
|
}`;
|
|
217
249
|
} else if (useWiderArithmetic) {
|
|
218
250
|
// Signed multiplication: compute in wider type, check bounds (Issue #94)
|
|
@@ -8,6 +8,11 @@ interface ICodeGeneratorOptions {
|
|
|
8
8
|
target?: string;
|
|
9
9
|
/** ADR-010: Source file path for validating includes */
|
|
10
10
|
sourcePath?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Issue #230: When true, emit self-include for extern "C" linkage.
|
|
13
|
+
* Only set this when headers will actually be generated alongside the implementation.
|
|
14
|
+
*/
|
|
15
|
+
generateHeaders?: boolean;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export default ICodeGeneratorOptions;
|
package/src/pipeline/Pipeline.ts
CHANGED
|
@@ -565,6 +565,8 @@ class Pipeline {
|
|
|
565
565
|
{
|
|
566
566
|
debugMode: this.config.debugMode,
|
|
567
567
|
target: this.config.target,
|
|
568
|
+
sourcePath: file.path, // Issue #230: For self-include header generation
|
|
569
|
+
generateHeaders: this.config.generateHeaders, // Issue #230: Enable self-include when headers are generated
|
|
568
570
|
},
|
|
569
571
|
);
|
|
570
572
|
|
|
@@ -724,6 +726,7 @@ class Pipeline {
|
|
|
724
726
|
workingDir?: string; // For resolving relative #includes
|
|
725
727
|
includeDirs?: string[]; // Additional include paths
|
|
726
728
|
sourcePath?: string; // Optional source path for error messages
|
|
729
|
+
generateHeaders?: boolean; // Issue #230: Enable self-include for extern "C" tests
|
|
727
730
|
},
|
|
728
731
|
): Promise<IFileResult> {
|
|
729
732
|
const workingDir = options?.workingDir ?? process.cwd();
|
|
@@ -868,6 +871,8 @@ class Pipeline {
|
|
|
868
871
|
{
|
|
869
872
|
debugMode: this.config.debugMode,
|
|
870
873
|
target: this.config.target,
|
|
874
|
+
sourcePath, // Issue #230: For self-include header generation
|
|
875
|
+
generateHeaders: options?.generateHeaders, // Issue #230: Enable self-include for extern "C" tests
|
|
871
876
|
},
|
|
872
877
|
);
|
|
873
878
|
|