c-next 0.1.69 → 0.1.70
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/package.json +1 -1
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +240 -204
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +693 -0
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +86 -5
- package/src/transpiler/{output/codegen → logic/analysis}/helpers/AssignmentTargetExtractor.ts +1 -1
- package/src/transpiler/{output/codegen → logic/analysis}/helpers/ChildStatementCollector.ts +1 -1
- package/src/transpiler/{output/codegen → logic/analysis}/helpers/StatementExpressionCollector.ts +1 -1
- package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/AssignmentTargetExtractor.test.ts +2 -2
- package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/ChildStatementCollector.test.ts +2 -2
- package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/StatementExpressionCollector.test.ts +2 -2
- package/src/transpiler/output/codegen/CodeGenerator.ts +35 -607
- package/src/transpiler/output/codegen/TypeRegistrationUtils.ts +4 -6
- package/src/transpiler/output/codegen/TypeResolver.ts +2 -2
- package/src/transpiler/output/codegen/TypeValidator.ts +5 -5
- package/src/transpiler/output/codegen/__tests__/TypeRegistrationUtils.test.ts +36 -51
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +20 -17
- package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +3 -3
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +1 -1
- package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +1 -1
- package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +1 -1
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +9 -9
- package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +12 -12
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +11 -11
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +23 -17
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +2 -2
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +3 -3
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +3 -3
- package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +4 -4
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +5 -5
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +23 -25
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +20 -36
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +18 -18
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/SpecialHandlers.test.ts +42 -32
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +5 -4
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +14 -6
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +19 -16
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +21 -4
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +15 -2
- package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +2 -1
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +2 -2
- package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +3 -3
- package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +1 -1
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +2 -2
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +1 -1
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +7 -7
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +7 -7
- package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +2 -2
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +4 -4
- package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +2 -2
- package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +5 -5
- package/src/transpiler/state/CodeGenState.ts +122 -4
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +269 -1
- /package/src/transpiler/{output/codegen → logic/analysis}/helpers/TransitiveModificationPropagator.ts +0 -0
- /package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/TransitiveModificationPropagator.test.ts +0 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass-By-Value Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Extracted from CodeGenerator.ts (Issue #269, #558, #566, #579)
|
|
5
|
+
*
|
|
6
|
+
* Performs three-phase analysis to determine which function parameters
|
|
7
|
+
* can be passed by value (as opposed to pointer):
|
|
8
|
+
*
|
|
9
|
+
* Phase 1: Collect function parameter lists and direct modifications
|
|
10
|
+
* Phase 2: Transitive modification propagation (via TransitiveModificationPropagator)
|
|
11
|
+
* Phase 3: Determine which parameters can pass by value
|
|
12
|
+
*
|
|
13
|
+
* A parameter can pass by value if:
|
|
14
|
+
* 1. It's a small primitive type (u8, i8, u16, i16, u32, i32, u64, i64, bool)
|
|
15
|
+
* 2. It's not modified (directly or transitively)
|
|
16
|
+
* 3. It's not an array, struct, string, or callback
|
|
17
|
+
* 4. It's not accessed via subscript (Issue #579)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
21
|
+
import CodeGenState from "../../state/CodeGenState";
|
|
22
|
+
import TransitiveModificationPropagator from "./helpers/TransitiveModificationPropagator";
|
|
23
|
+
import StatementExpressionCollector from "./helpers/StatementExpressionCollector";
|
|
24
|
+
import ChildStatementCollector from "./helpers/ChildStatementCollector";
|
|
25
|
+
import AssignmentTargetExtractor from "./helpers/AssignmentTargetExtractor";
|
|
26
|
+
import ExpressionUtils from "../../../utils/ExpressionUtils";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Small primitive types that are eligible for pass-by-value optimization.
|
|
30
|
+
*/
|
|
31
|
+
const SMALL_PRIMITIVES = new Set([
|
|
32
|
+
"u8",
|
|
33
|
+
"i8",
|
|
34
|
+
"u16",
|
|
35
|
+
"i16",
|
|
36
|
+
"u32",
|
|
37
|
+
"i32",
|
|
38
|
+
"u64",
|
|
39
|
+
"i64",
|
|
40
|
+
"bool",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Static analyzer for determining pass-by-value eligibility.
|
|
45
|
+
* All state is stored in CodeGenState - this class contains pure analysis logic.
|
|
46
|
+
*/
|
|
47
|
+
class PassByValueAnalyzer {
|
|
48
|
+
/**
|
|
49
|
+
* Main entry point: Analyze a program tree to determine pass-by-value parameters.
|
|
50
|
+
* Updates CodeGenState with analysis results.
|
|
51
|
+
*/
|
|
52
|
+
static analyze(tree: Parser.ProgramContext): void {
|
|
53
|
+
// Reset analysis state
|
|
54
|
+
CodeGenState.modifiedParameters.clear();
|
|
55
|
+
CodeGenState.passByValueParams.clear();
|
|
56
|
+
CodeGenState.functionCallGraph.clear();
|
|
57
|
+
CodeGenState.functionParamLists.clear();
|
|
58
|
+
|
|
59
|
+
// Phase 1: Collect function parameter lists and direct modifications
|
|
60
|
+
PassByValueAnalyzer.collectFunctionParametersAndModifications(tree);
|
|
61
|
+
|
|
62
|
+
// Issue #558: Inject cross-file data before transitive propagation
|
|
63
|
+
PassByValueAnalyzer.injectCrossFileModifications();
|
|
64
|
+
PassByValueAnalyzer.injectCrossFileParamLists();
|
|
65
|
+
|
|
66
|
+
// Phase 2: Fixed-point iteration for transitive modifications
|
|
67
|
+
TransitiveModificationPropagator.propagate(
|
|
68
|
+
CodeGenState.functionCallGraph,
|
|
69
|
+
CodeGenState.functionParamLists,
|
|
70
|
+
CodeGenState.modifiedParameters,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Phase 3: Determine which parameters can pass by value
|
|
74
|
+
PassByValueAnalyzer.computePassByValueParams();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Inject cross-file modification data into modifiedParameters.
|
|
79
|
+
* SonarCloud S3776: Extracted from analyze().
|
|
80
|
+
*/
|
|
81
|
+
private static injectCrossFileModifications(): void {
|
|
82
|
+
if (!CodeGenState.pendingCrossFileModifications) return;
|
|
83
|
+
|
|
84
|
+
for (const [
|
|
85
|
+
funcName,
|
|
86
|
+
params,
|
|
87
|
+
] of CodeGenState.pendingCrossFileModifications) {
|
|
88
|
+
const existing = CodeGenState.modifiedParameters.get(funcName);
|
|
89
|
+
if (existing) {
|
|
90
|
+
for (const param of params) {
|
|
91
|
+
existing.add(param);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
CodeGenState.modifiedParameters.set(funcName, new Set(params));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
CodeGenState.pendingCrossFileModifications = null; // Clear after use
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Inject cross-file parameter lists into functionParamLists.
|
|
102
|
+
* SonarCloud S3776: Extracted from analyze().
|
|
103
|
+
*/
|
|
104
|
+
private static injectCrossFileParamLists(): void {
|
|
105
|
+
if (!CodeGenState.pendingCrossFileParamLists) return;
|
|
106
|
+
|
|
107
|
+
for (const [funcName, params] of CodeGenState.pendingCrossFileParamLists) {
|
|
108
|
+
if (!CodeGenState.functionParamLists.has(funcName)) {
|
|
109
|
+
CodeGenState.functionParamLists.set(funcName, [...params]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
CodeGenState.pendingCrossFileParamLists = null; // Clear after use
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Phase 1: Walk all functions to collect:
|
|
117
|
+
* - Parameter lists (for call graph resolution)
|
|
118
|
+
* - Direct modifications (param <- value)
|
|
119
|
+
* - Function calls where params are passed as arguments
|
|
120
|
+
*
|
|
121
|
+
* Exposed as public for use by CodeGenerator.analyzeModificationsOnly()
|
|
122
|
+
* which needs to run just this phase for cross-file analysis.
|
|
123
|
+
*/
|
|
124
|
+
static collectFunctionParametersAndModifications(
|
|
125
|
+
tree: Parser.ProgramContext,
|
|
126
|
+
): void {
|
|
127
|
+
for (const decl of tree.declaration()) {
|
|
128
|
+
// Handle scope-level functions
|
|
129
|
+
if (decl.scopeDeclaration()) {
|
|
130
|
+
const scopeDecl = decl.scopeDeclaration()!;
|
|
131
|
+
const scopeName = scopeDecl.IDENTIFIER().getText();
|
|
132
|
+
|
|
133
|
+
for (const member of scopeDecl.scopeMember()) {
|
|
134
|
+
if (member.functionDeclaration()) {
|
|
135
|
+
const funcDecl = member.functionDeclaration()!;
|
|
136
|
+
const funcName = funcDecl.IDENTIFIER().getText();
|
|
137
|
+
const fullName = `${scopeName}_${funcName}`;
|
|
138
|
+
PassByValueAnalyzer.analyzeFunctionForModifications(
|
|
139
|
+
fullName,
|
|
140
|
+
funcDecl,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Handle top-level functions
|
|
147
|
+
if (decl.functionDeclaration()) {
|
|
148
|
+
const funcDecl = decl.functionDeclaration()!;
|
|
149
|
+
const name = funcDecl.IDENTIFIER().getText();
|
|
150
|
+
PassByValueAnalyzer.analyzeFunctionForModifications(name, funcDecl);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Analyze a single function for parameter modifications and call graph edges.
|
|
157
|
+
*/
|
|
158
|
+
private static analyzeFunctionForModifications(
|
|
159
|
+
funcName: string,
|
|
160
|
+
funcDecl: Parser.FunctionDeclarationContext,
|
|
161
|
+
): void {
|
|
162
|
+
// Collect parameter names
|
|
163
|
+
const paramNames: string[] = [];
|
|
164
|
+
const paramList = funcDecl.parameterList();
|
|
165
|
+
if (paramList) {
|
|
166
|
+
for (const param of paramList.parameter()) {
|
|
167
|
+
paramNames.push(param.IDENTIFIER().getText());
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
CodeGenState.functionParamLists.set(funcName, paramNames);
|
|
171
|
+
|
|
172
|
+
// Initialize modified set
|
|
173
|
+
CodeGenState.modifiedParameters.set(funcName, new Set());
|
|
174
|
+
// Issue #579: Initialize subscript access tracking
|
|
175
|
+
CodeGenState.subscriptAccessedParameters.set(funcName, new Set());
|
|
176
|
+
CodeGenState.functionCallGraph.set(funcName, []);
|
|
177
|
+
|
|
178
|
+
// Walk the function body to find modifications and calls
|
|
179
|
+
const block = funcDecl.block();
|
|
180
|
+
if (block) {
|
|
181
|
+
PassByValueAnalyzer.walkBlockForModifications(
|
|
182
|
+
funcName,
|
|
183
|
+
paramNames,
|
|
184
|
+
block,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Walk a block to find parameter modifications and function calls.
|
|
191
|
+
*/
|
|
192
|
+
private static walkBlockForModifications(
|
|
193
|
+
funcName: string,
|
|
194
|
+
paramNames: string[],
|
|
195
|
+
block: Parser.BlockContext,
|
|
196
|
+
): void {
|
|
197
|
+
const paramSet = new Set(paramNames);
|
|
198
|
+
|
|
199
|
+
for (const stmt of block.statement()) {
|
|
200
|
+
PassByValueAnalyzer.walkStatementForModifications(
|
|
201
|
+
funcName,
|
|
202
|
+
paramSet,
|
|
203
|
+
stmt,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Walk a statement recursively looking for modifications and calls.
|
|
210
|
+
* Issue #566: Refactored to use helper methods for expression and child collection.
|
|
211
|
+
*/
|
|
212
|
+
private static walkStatementForModifications(
|
|
213
|
+
funcName: string,
|
|
214
|
+
paramSet: Set<string>,
|
|
215
|
+
stmt: Parser.StatementContext,
|
|
216
|
+
): void {
|
|
217
|
+
// 1. Check for parameter modifications via assignment targets
|
|
218
|
+
if (stmt.assignmentStatement()) {
|
|
219
|
+
PassByValueAnalyzer.trackAssignmentModifications(
|
|
220
|
+
funcName,
|
|
221
|
+
paramSet,
|
|
222
|
+
stmt,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 2. Walk all expressions in this statement for function calls and subscript access
|
|
227
|
+
for (const expr of StatementExpressionCollector.collectAll(stmt)) {
|
|
228
|
+
PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, expr);
|
|
229
|
+
// Issue #579: Also track subscript read access on parameters
|
|
230
|
+
PassByValueAnalyzer.walkExpressionForSubscriptAccess(
|
|
231
|
+
funcName,
|
|
232
|
+
paramSet,
|
|
233
|
+
expr,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 3. Recurse into child statements and blocks
|
|
238
|
+
const { statements, blocks } = ChildStatementCollector.collectAll(stmt);
|
|
239
|
+
for (const childStmt of statements) {
|
|
240
|
+
PassByValueAnalyzer.walkStatementForModifications(
|
|
241
|
+
funcName,
|
|
242
|
+
paramSet,
|
|
243
|
+
childStmt,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
for (const block of blocks) {
|
|
247
|
+
PassByValueAnalyzer.walkBlockForModifications(
|
|
248
|
+
funcName,
|
|
249
|
+
[...paramSet],
|
|
250
|
+
block,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Track assignment modifications for parameter const inference.
|
|
257
|
+
* SonarCloud S3776: Extracted from walkStatementForModifications().
|
|
258
|
+
*/
|
|
259
|
+
private static trackAssignmentModifications(
|
|
260
|
+
funcName: string,
|
|
261
|
+
paramSet: Set<string>,
|
|
262
|
+
stmt: Parser.StatementContext,
|
|
263
|
+
): void {
|
|
264
|
+
const assign = stmt.assignmentStatement()!;
|
|
265
|
+
const target = assign.assignmentTarget();
|
|
266
|
+
|
|
267
|
+
const { baseIdentifier, hasSingleIndexSubscript } =
|
|
268
|
+
AssignmentTargetExtractor.extract(target);
|
|
269
|
+
|
|
270
|
+
// Issue #579: Track subscript access on parameters (for write path)
|
|
271
|
+
if (
|
|
272
|
+
hasSingleIndexSubscript &&
|
|
273
|
+
baseIdentifier &&
|
|
274
|
+
paramSet.has(baseIdentifier)
|
|
275
|
+
) {
|
|
276
|
+
CodeGenState.subscriptAccessedParameters
|
|
277
|
+
.get(funcName)!
|
|
278
|
+
.add(baseIdentifier);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Track as modified parameter
|
|
282
|
+
if (baseIdentifier && paramSet.has(baseIdentifier)) {
|
|
283
|
+
CodeGenState.modifiedParameters.get(funcName)!.add(baseIdentifier);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Walk an expression tree to find function calls where parameters are passed.
|
|
289
|
+
* Uses recursive descent through the expression hierarchy.
|
|
290
|
+
*/
|
|
291
|
+
private static walkExpressionForCalls(
|
|
292
|
+
funcName: string,
|
|
293
|
+
paramSet: Set<string>,
|
|
294
|
+
expr: Parser.ExpressionContext,
|
|
295
|
+
): void {
|
|
296
|
+
// Expression -> TernaryExpression -> OrExpression -> ... -> PostfixExpression
|
|
297
|
+
const ternary = expr.ternaryExpression();
|
|
298
|
+
if (ternary) {
|
|
299
|
+
// Walk all orExpression children
|
|
300
|
+
for (const orExpr of ternary.orExpression()) {
|
|
301
|
+
PassByValueAnalyzer.walkOrExpressionForCalls(
|
|
302
|
+
funcName,
|
|
303
|
+
paramSet,
|
|
304
|
+
orExpr,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Issue #579: Walk an expression tree to find subscript access on parameters.
|
|
312
|
+
* This tracks read access like `buf[i]` where buf is a parameter.
|
|
313
|
+
* Parameters with subscript access must become pointers.
|
|
314
|
+
*/
|
|
315
|
+
private static walkExpressionForSubscriptAccess(
|
|
316
|
+
funcName: string,
|
|
317
|
+
paramSet: Set<string>,
|
|
318
|
+
expr: Parser.ExpressionContext,
|
|
319
|
+
): void {
|
|
320
|
+
const ternary = expr.ternaryExpression();
|
|
321
|
+
if (ternary) {
|
|
322
|
+
for (const orExpr of ternary.orExpression()) {
|
|
323
|
+
PassByValueAnalyzer.walkOrExpression(orExpr, (unaryExpr) => {
|
|
324
|
+
PassByValueAnalyzer.handleSubscriptAccess(
|
|
325
|
+
funcName,
|
|
326
|
+
paramSet,
|
|
327
|
+
unaryExpr,
|
|
328
|
+
);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Issue #579: Handle subscript access on a unary expression.
|
|
336
|
+
* Only tracks single-index subscript access (which could be array access).
|
|
337
|
+
* Two-index subscript (e.g., value[start, width]) is always bit extraction,
|
|
338
|
+
* so it doesn't require the parameter to become a pointer.
|
|
339
|
+
*/
|
|
340
|
+
private static handleSubscriptAccess(
|
|
341
|
+
funcName: string,
|
|
342
|
+
paramSet: Set<string>,
|
|
343
|
+
unaryExpr: Parser.UnaryExpressionContext,
|
|
344
|
+
): void {
|
|
345
|
+
const postfixExpr = unaryExpr.postfixExpression();
|
|
346
|
+
if (!postfixExpr) return;
|
|
347
|
+
|
|
348
|
+
const primary = postfixExpr.primaryExpression();
|
|
349
|
+
const ops = postfixExpr.postfixOp();
|
|
350
|
+
|
|
351
|
+
// Check if primary is a parameter and there's subscript access
|
|
352
|
+
const primaryId = primary.IDENTIFIER()?.getText();
|
|
353
|
+
if (!primaryId || !paramSet.has(primaryId)) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Only track SINGLE-index subscript access (potential array access)
|
|
358
|
+
// Two-index subscript like value[0, 8] is bit extraction, not array access
|
|
359
|
+
const hasSingleIndexSubscript = ops.some(
|
|
360
|
+
(op) => op.expression().length === 1,
|
|
361
|
+
);
|
|
362
|
+
if (hasSingleIndexSubscript) {
|
|
363
|
+
CodeGenState.subscriptAccessedParameters.get(funcName)!.add(primaryId);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Generic walker for orExpression trees.
|
|
369
|
+
* Walks through the expression hierarchy and calls the handler for each unaryExpression.
|
|
370
|
+
* Used by both function call tracking and subscript access tracking.
|
|
371
|
+
*/
|
|
372
|
+
private static walkOrExpression(
|
|
373
|
+
orExpr: Parser.OrExpressionContext,
|
|
374
|
+
handler: (unaryExpr: Parser.UnaryExpressionContext) => void,
|
|
375
|
+
): void {
|
|
376
|
+
orExpr
|
|
377
|
+
.andExpression()
|
|
378
|
+
.flatMap((and) => and.equalityExpression())
|
|
379
|
+
.flatMap((eq) => eq.relationalExpression())
|
|
380
|
+
.flatMap((rel) => rel.bitwiseOrExpression())
|
|
381
|
+
.flatMap((bor) => bor.bitwiseXorExpression())
|
|
382
|
+
.flatMap((bxor) => bxor.bitwiseAndExpression())
|
|
383
|
+
.flatMap((band) => band.shiftExpression())
|
|
384
|
+
.flatMap((shift) => shift.additiveExpression())
|
|
385
|
+
.flatMap((add) => add.multiplicativeExpression())
|
|
386
|
+
.flatMap((mul) => mul.unaryExpression())
|
|
387
|
+
.forEach(handler);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Walk an orExpression tree for function calls.
|
|
392
|
+
*/
|
|
393
|
+
private static walkOrExpressionForCalls(
|
|
394
|
+
funcName: string,
|
|
395
|
+
paramSet: Set<string>,
|
|
396
|
+
orExpr: Parser.OrExpressionContext,
|
|
397
|
+
): void {
|
|
398
|
+
PassByValueAnalyzer.walkOrExpression(orExpr, (unaryExpr) => {
|
|
399
|
+
PassByValueAnalyzer.walkUnaryExpressionForCalls(
|
|
400
|
+
funcName,
|
|
401
|
+
paramSet,
|
|
402
|
+
unaryExpr,
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Walk a unaryExpression tree for function calls.
|
|
409
|
+
*/
|
|
410
|
+
private static walkUnaryExpressionForCalls(
|
|
411
|
+
funcName: string,
|
|
412
|
+
paramSet: Set<string>,
|
|
413
|
+
unaryExpr: Parser.UnaryExpressionContext,
|
|
414
|
+
): void {
|
|
415
|
+
// Recurse into nested unary
|
|
416
|
+
if (unaryExpr.unaryExpression()) {
|
|
417
|
+
PassByValueAnalyzer.walkUnaryExpressionForCalls(
|
|
418
|
+
funcName,
|
|
419
|
+
paramSet,
|
|
420
|
+
unaryExpr.unaryExpression()!,
|
|
421
|
+
);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Check postfix expression
|
|
426
|
+
const postfix = unaryExpr.postfixExpression();
|
|
427
|
+
if (postfix) {
|
|
428
|
+
PassByValueAnalyzer.walkPostfixExpressionForCalls(
|
|
429
|
+
funcName,
|
|
430
|
+
paramSet,
|
|
431
|
+
postfix,
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Walk a postfixExpression for function calls.
|
|
438
|
+
* This is where function calls are found: primaryExpr followed by '(' args ')'
|
|
439
|
+
*/
|
|
440
|
+
private static walkPostfixExpressionForCalls(
|
|
441
|
+
funcName: string,
|
|
442
|
+
paramSet: Set<string>,
|
|
443
|
+
postfix: Parser.PostfixExpressionContext,
|
|
444
|
+
): void {
|
|
445
|
+
const primary = postfix.primaryExpression();
|
|
446
|
+
const postfixOps = postfix.postfixOp();
|
|
447
|
+
|
|
448
|
+
// Handle simple function calls: IDENTIFIER followed by '(' ... ')'
|
|
449
|
+
PassByValueAnalyzer.handleSimpleFunctionCall(
|
|
450
|
+
funcName,
|
|
451
|
+
paramSet,
|
|
452
|
+
primary,
|
|
453
|
+
postfixOps,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
// Issue #365: Handle scope-qualified calls: Scope.method(...) or global.Scope.method(...)
|
|
457
|
+
PassByValueAnalyzer.handleScopeQualifiedCalls(
|
|
458
|
+
funcName,
|
|
459
|
+
paramSet,
|
|
460
|
+
primary,
|
|
461
|
+
postfixOps,
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
// Recurse into primary expression if it's a parenthesized expression
|
|
465
|
+
if (primary.expression()) {
|
|
466
|
+
PassByValueAnalyzer.walkExpressionForCalls(
|
|
467
|
+
funcName,
|
|
468
|
+
paramSet,
|
|
469
|
+
primary.expression()!,
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Walk arguments in any postfix function call ops (for nested calls)
|
|
474
|
+
PassByValueAnalyzer.walkPostfixOpsRecursively(
|
|
475
|
+
funcName,
|
|
476
|
+
paramSet,
|
|
477
|
+
postfixOps,
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Handle simple function calls: IDENTIFIER followed by '(' ... ')'
|
|
483
|
+
*/
|
|
484
|
+
private static handleSimpleFunctionCall(
|
|
485
|
+
funcName: string,
|
|
486
|
+
paramSet: Set<string>,
|
|
487
|
+
primary: Parser.PrimaryExpressionContext,
|
|
488
|
+
postfixOps: Parser.PostfixOpContext[],
|
|
489
|
+
): void {
|
|
490
|
+
if (!primary.IDENTIFIER() || postfixOps.length === 0) return;
|
|
491
|
+
|
|
492
|
+
const firstOp = postfixOps[0];
|
|
493
|
+
if (!firstOp.LPAREN()) return;
|
|
494
|
+
|
|
495
|
+
const calleeName = primary.IDENTIFIER()!.getText();
|
|
496
|
+
PassByValueAnalyzer.recordCallsFromArgList(
|
|
497
|
+
funcName,
|
|
498
|
+
paramSet,
|
|
499
|
+
calleeName,
|
|
500
|
+
firstOp,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Handle scope-qualified calls: Scope.method(...) or global.Scope.method(...)
|
|
506
|
+
* Track member accesses to build the mangled callee name (e.g., Storage_load)
|
|
507
|
+
*/
|
|
508
|
+
private static handleScopeQualifiedCalls(
|
|
509
|
+
funcName: string,
|
|
510
|
+
paramSet: Set<string>,
|
|
511
|
+
primary: Parser.PrimaryExpressionContext,
|
|
512
|
+
postfixOps: Parser.PostfixOpContext[],
|
|
513
|
+
): void {
|
|
514
|
+
if (postfixOps.length === 0) return;
|
|
515
|
+
|
|
516
|
+
const memberNames = PassByValueAnalyzer.collectInitialMemberNames(
|
|
517
|
+
funcName,
|
|
518
|
+
primary,
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
for (const op of postfixOps) {
|
|
522
|
+
if (op.IDENTIFIER()) {
|
|
523
|
+
memberNames.push(op.IDENTIFIER()!.getText());
|
|
524
|
+
} else if (op.LPAREN() && memberNames.length >= 1) {
|
|
525
|
+
const calleeName = memberNames.join("_");
|
|
526
|
+
PassByValueAnalyzer.recordCallsFromArgList(
|
|
527
|
+
funcName,
|
|
528
|
+
paramSet,
|
|
529
|
+
calleeName,
|
|
530
|
+
op,
|
|
531
|
+
);
|
|
532
|
+
memberNames.length = 0; // Reset for potential chained calls
|
|
533
|
+
} else if (op.expression().length > 0) {
|
|
534
|
+
memberNames.length = 0; // Array subscript breaks scope chain
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Collect initial member names from primary expression for scope resolution.
|
|
541
|
+
* Issue #561: When 'this' is used, resolve to the current scope name from funcName.
|
|
542
|
+
*/
|
|
543
|
+
private static collectInitialMemberNames(
|
|
544
|
+
funcName: string,
|
|
545
|
+
primary: Parser.PrimaryExpressionContext,
|
|
546
|
+
): string[] {
|
|
547
|
+
const memberNames: string[] = [];
|
|
548
|
+
const primaryId = primary.IDENTIFIER()?.getText();
|
|
549
|
+
|
|
550
|
+
if (primaryId && primaryId !== "global") {
|
|
551
|
+
memberNames.push(primaryId);
|
|
552
|
+
} else if (primary.THIS()) {
|
|
553
|
+
const scopeName = funcName.split("_")[0];
|
|
554
|
+
if (scopeName && scopeName !== funcName) {
|
|
555
|
+
memberNames.push(scopeName);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return memberNames;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Record function calls to the call graph from an argument list.
|
|
563
|
+
* Also recurses into argument expressions.
|
|
564
|
+
*/
|
|
565
|
+
private static recordCallsFromArgList(
|
|
566
|
+
funcName: string,
|
|
567
|
+
paramSet: Set<string>,
|
|
568
|
+
calleeName: string,
|
|
569
|
+
op: Parser.PostfixOpContext,
|
|
570
|
+
): void {
|
|
571
|
+
const argList = op.argumentList();
|
|
572
|
+
if (!argList) return;
|
|
573
|
+
|
|
574
|
+
const args = argList.expression();
|
|
575
|
+
for (let i = 0; i < args.length; i++) {
|
|
576
|
+
const arg = args[i];
|
|
577
|
+
const argName = ExpressionUtils.extractIdentifier(arg);
|
|
578
|
+
if (argName && paramSet.has(argName)) {
|
|
579
|
+
CodeGenState.functionCallGraph.get(funcName)!.push({
|
|
580
|
+
callee: calleeName,
|
|
581
|
+
paramIndex: i,
|
|
582
|
+
argParamName: argName,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, arg);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Walk postfix ops recursively for nested calls and array subscripts.
|
|
591
|
+
*/
|
|
592
|
+
private static walkPostfixOpsRecursively(
|
|
593
|
+
funcName: string,
|
|
594
|
+
paramSet: Set<string>,
|
|
595
|
+
postfixOps: Parser.PostfixOpContext[],
|
|
596
|
+
): void {
|
|
597
|
+
for (const op of postfixOps) {
|
|
598
|
+
if (op.argumentList()) {
|
|
599
|
+
for (const argExpr of op.argumentList()!.expression()) {
|
|
600
|
+
PassByValueAnalyzer.walkExpressionForCalls(
|
|
601
|
+
funcName,
|
|
602
|
+
paramSet,
|
|
603
|
+
argExpr,
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const expr of op.expression()) {
|
|
608
|
+
PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, expr);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Phase 3: Determine which parameters can pass by value.
|
|
615
|
+
* A parameter passes by value if:
|
|
616
|
+
* 1. It's a small primitive type (u8, i8, u16, i16, u32, i32, u64, i64, bool)
|
|
617
|
+
* 2. It's not modified (directly or transitively)
|
|
618
|
+
* 3. It's not an array, struct, string, or callback
|
|
619
|
+
*/
|
|
620
|
+
private static computePassByValueParams(): void {
|
|
621
|
+
for (const [funcName, paramNames] of CodeGenState.functionParamLists) {
|
|
622
|
+
const passByValue = new Set<string>();
|
|
623
|
+
const modified =
|
|
624
|
+
CodeGenState.modifiedParameters.get(funcName) ?? new Set();
|
|
625
|
+
|
|
626
|
+
// Get function declaration to check parameter types
|
|
627
|
+
const funcSig = CodeGenState.functionSignatures.get(funcName);
|
|
628
|
+
if (funcSig) {
|
|
629
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
630
|
+
const paramName = paramNames[i];
|
|
631
|
+
const paramSig = funcSig.parameters[i];
|
|
632
|
+
|
|
633
|
+
if (!paramSig) continue;
|
|
634
|
+
|
|
635
|
+
// Check if eligible for pass-by-value:
|
|
636
|
+
// - Is a small primitive type
|
|
637
|
+
// - Not an array
|
|
638
|
+
// - Not modified
|
|
639
|
+
// - Not accessed via subscript (Issue #579)
|
|
640
|
+
const isSmallPrimitive = SMALL_PRIMITIVES.has(paramSig.baseType);
|
|
641
|
+
const isArray = paramSig.isArray ?? false;
|
|
642
|
+
const isModified = modified.has(paramName);
|
|
643
|
+
// Issue #579: Parameters with subscript access must become pointers
|
|
644
|
+
const hasSubscriptAccess =
|
|
645
|
+
CodeGenState.subscriptAccessedParameters
|
|
646
|
+
.get(funcName)
|
|
647
|
+
?.has(paramName) ?? false;
|
|
648
|
+
|
|
649
|
+
if (
|
|
650
|
+
isSmallPrimitive &&
|
|
651
|
+
!isArray &&
|
|
652
|
+
!isModified &&
|
|
653
|
+
!hasSubscriptAccess
|
|
654
|
+
) {
|
|
655
|
+
passByValue.add(paramName);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
CodeGenState.passByValueParams.set(funcName, passByValue);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Check if a parameter should be passed by value (by name).
|
|
666
|
+
* Used internally during code generation.
|
|
667
|
+
*/
|
|
668
|
+
static isParameterPassByValueByName(
|
|
669
|
+
funcName: string,
|
|
670
|
+
paramName: string,
|
|
671
|
+
): boolean {
|
|
672
|
+
const passByValue = CodeGenState.passByValueParams.get(funcName);
|
|
673
|
+
return passByValue?.has(paramName) ?? false;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Issue #269: Check if a parameter should be passed by value (by index).
|
|
678
|
+
* Part of IOrchestrator interface - used by CallExprGenerator.
|
|
679
|
+
*/
|
|
680
|
+
static isParameterPassByValue(funcName: string, paramIndex: number): boolean {
|
|
681
|
+
const paramList = CodeGenState.functionParamLists.get(funcName);
|
|
682
|
+
if (!paramList || paramIndex < 0 || paramIndex >= paramList.length) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
const paramName = paramList[paramIndex];
|
|
686
|
+
return PassByValueAnalyzer.isParameterPassByValueByName(
|
|
687
|
+
funcName,
|
|
688
|
+
paramName,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export default PassByValueAnalyzer;
|