gofront 1.4.0 → 1.6.0
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 +174 -403
- package/package.json +16 -19
- package/src/{codegen → backend/js}/expressions.js +265 -77
- package/src/{codegen → backend/js}/index.js +120 -89
- package/src/{codegen → backend/js}/runtime.js +102 -0
- package/src/{codegen → backend/js}/source-map.js +32 -0
- package/src/{codegen → backend/js}/statements.js +139 -64
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
- package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
- package/src/{codegen → backend/js}/stdlib/index.js +6 -0
- package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
- package/src/{codegen → backend/js}/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
- package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +58 -0
- package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
- package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
- package/src/{codegen → backend/js}/stdlib/time.js +3 -3
- package/src/{codegen → backend/js}/templ.js +3 -3
- package/src/backend/wasm/boundary.js +1798 -0
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +1376 -0
- package/src/backend/wasm/encode.js +937 -0
- package/src/backend/wasm/glue.js +144 -0
- package/src/backend/wasm/index.js +2790 -0
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +544 -0
- package/src/backend/wasm/wat.js +302 -0
- package/src/cli-core.js +323 -75
- package/src/compiler.js +447 -14
- package/src/dev-server.js +56 -15
- package/src/index.js +99 -120
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +35 -0
- package/src/lower/captures.js +172 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +164 -0
- package/src/lower/functions.js +302 -0
- package/src/lower/globals.js +316 -0
- package/src/lower/index.js +118 -0
- package/src/lower/ownership.js +137 -0
- package/src/lower/range.js +67 -0
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +3 -1
- package/src/test-runner.js +94 -20
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +182 -3
- package/src/typechecker/index.js +261 -5
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/stdlib/core.js +34 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +259 -6
- package/src/vendor.js +2 -2
- package/src/codegen/stdlib/strconv.js +0 -33
- /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// src/lower/globals.js
|
|
2
|
+
// Identifies package-level globals that are never assigned outside init,
|
|
3
|
+
// and computes functions that participate in the package initialization call path.
|
|
4
|
+
|
|
5
|
+
import { isReferenceType } from "./boxing.js";
|
|
6
|
+
import { forEachChild, someChild } from "./walk.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Computes:
|
|
10
|
+
* - neverAssignedGlobals: Set of package variable names that are never reassigned outside init.
|
|
11
|
+
* - initPathFunctions: Set of function/method names reachable during package initialization.
|
|
12
|
+
* - cachedGlobalsByFunc: Map of AST function declarations to list of global names to cache.
|
|
13
|
+
*
|
|
14
|
+
* @param {Array<Object>} programs
|
|
15
|
+
* @param {Object} [_checker]
|
|
16
|
+
* @returns {{ neverAssignedGlobals: Set<string>, initPathFunctions: Set<string>, cachedGlobalsByFunc: Map<Object, Array<string>> }}
|
|
17
|
+
*/
|
|
18
|
+
export function computeGlobalAnalysis(programs, _checker = null) {
|
|
19
|
+
const progs = Array.isArray(programs) ? programs : [programs];
|
|
20
|
+
const packageGlobals = collectPackageGlobals(progs);
|
|
21
|
+
const reassigned = collectReassigned(progs, packageGlobals);
|
|
22
|
+
|
|
23
|
+
const neverAssignedGlobals = new Set();
|
|
24
|
+
for (const g of packageGlobals) {
|
|
25
|
+
if (!reassigned.has(g)) neverAssignedGlobals.add(g);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const initPathFunctions = buildInitPathFunctions(progs);
|
|
29
|
+
const cachedGlobalsByFunc = buildCachedGlobalsMap(
|
|
30
|
+
progs,
|
|
31
|
+
neverAssignedGlobals,
|
|
32
|
+
initPathFunctions,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return { neverAssignedGlobals, initPathFunctions, cachedGlobalsByFunc };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function collectVarNames(spec, out) {
|
|
39
|
+
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
40
|
+
for (const n of names) {
|
|
41
|
+
const name = typeof n === "string" ? n : n?.name;
|
|
42
|
+
if (name && name !== "_") out.add(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function collectPackageGlobals(programs) {
|
|
47
|
+
const out = new Set();
|
|
48
|
+
for (const p of programs) {
|
|
49
|
+
for (const d of p?.decls ?? []) {
|
|
50
|
+
if (d.kind === "VarDecl") {
|
|
51
|
+
for (const spec of d.decls ?? d.specs ?? [d]) {
|
|
52
|
+
collectVarNames(spec, out);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function collectReassigned(programs, packageGlobals) {
|
|
61
|
+
const reassigned = new Set();
|
|
62
|
+
for (const p of programs) {
|
|
63
|
+
for (const d of p?.decls ?? []) {
|
|
64
|
+
if (d.kind === "FuncDecl" || d.kind === "MethodDecl") {
|
|
65
|
+
const isInit =
|
|
66
|
+
d.kind === "FuncDecl" &&
|
|
67
|
+
(d.name === "init" || d.name.startsWith("init$"));
|
|
68
|
+
scanReassignments(d.body, packageGlobals, reassigned, isInit);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return reassigned;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function checkAssignLhs(lhs, packageGlobals, reassigned) {
|
|
76
|
+
for (const l of lhs ?? []) {
|
|
77
|
+
if (l.kind === "Ident" && packageGlobals.has(l.name)) {
|
|
78
|
+
reassigned.add(l.name);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function checkIncDec(stmt, packageGlobals, reassigned) {
|
|
84
|
+
if (stmt.expr?.kind === "Ident" && packageGlobals.has(stmt.expr.name)) {
|
|
85
|
+
reassigned.add(stmt.expr.name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function checkAddressTaken(node, packageGlobals, reassigned) {
|
|
90
|
+
if (
|
|
91
|
+
node.kind === "Ident" &&
|
|
92
|
+
node._addressTaken &&
|
|
93
|
+
packageGlobals.has(node.name)
|
|
94
|
+
) {
|
|
95
|
+
if (!isReferenceType(node._type)) {
|
|
96
|
+
reassigned.add(node.name);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function scanReassignments(node, packageGlobals, reassigned, insideInit) {
|
|
102
|
+
if (!node || typeof node !== "object") return;
|
|
103
|
+
if (Array.isArray(node)) {
|
|
104
|
+
for (const child of node) {
|
|
105
|
+
scanReassignments(child, packageGlobals, reassigned, insideInit);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (node.kind === "FuncLit") {
|
|
110
|
+
scanReassignments(node.body, packageGlobals, reassigned, false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (!insideInit) {
|
|
114
|
+
if (node.kind === "AssignStmt" || node.kind === "CompoundAssignStmt") {
|
|
115
|
+
checkAssignLhs(node.lhs, packageGlobals, reassigned);
|
|
116
|
+
} else if (node.kind === "IncDecStmt") {
|
|
117
|
+
checkIncDec(node, packageGlobals, reassigned);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
checkAddressTaken(node, packageGlobals, reassigned);
|
|
121
|
+
forEachChild(node, (child) =>
|
|
122
|
+
scanReassignments(child, packageGlobals, reassigned, insideInit),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function addCallTarget(call, called) {
|
|
127
|
+
if (call.func?.kind === "Ident") {
|
|
128
|
+
called.add(call.func.name);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (call.func?.kind === "SelectorExpr") {
|
|
132
|
+
const sel = call.func.sel ?? call.func.field;
|
|
133
|
+
if (sel) called.add(sel);
|
|
134
|
+
const typeName =
|
|
135
|
+
call.func.expr?._type?.name ?? call.func.expr?._type?.base?.name;
|
|
136
|
+
if (typeName && sel) called.add(`${typeName}.${sel}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function findCalledFunctions(node, called = new Set()) {
|
|
141
|
+
if (!node || typeof node !== "object") return called;
|
|
142
|
+
if (Array.isArray(node)) {
|
|
143
|
+
for (const child of node) findCalledFunctions(child, called);
|
|
144
|
+
return called;
|
|
145
|
+
}
|
|
146
|
+
if (node.kind === "CallExpr") addCallTarget(node, called);
|
|
147
|
+
forEachChild(node, (child) => findCalledFunctions(child, called));
|
|
148
|
+
return called;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function addCallees(callGraph, caller, callees) {
|
|
152
|
+
let set = callGraph.get(caller);
|
|
153
|
+
if (!set) {
|
|
154
|
+
set = new Set();
|
|
155
|
+
callGraph.set(caller, set);
|
|
156
|
+
}
|
|
157
|
+
for (const c of callees) set.add(c);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildInitSeeds(programs) {
|
|
161
|
+
const seeds = new Set(["init", "__init_globals"]);
|
|
162
|
+
for (const p of programs) {
|
|
163
|
+
for (const d of p?.decls ?? []) {
|
|
164
|
+
if (
|
|
165
|
+
d.kind === "FuncDecl" &&
|
|
166
|
+
(d.name === "init" || d.name.startsWith("init$"))
|
|
167
|
+
) {
|
|
168
|
+
seeds.add(d.name);
|
|
169
|
+
}
|
|
170
|
+
if (d.kind === "VarDecl") {
|
|
171
|
+
for (const spec of d.decls ?? d.specs ?? [d]) {
|
|
172
|
+
const values = spec.value ?? (spec.init ? [spec.init] : []);
|
|
173
|
+
for (const val of values) findCalledFunctions(val, seeds);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return seeds;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildCallGraph(programs) {
|
|
182
|
+
const callGraph = new Map();
|
|
183
|
+
for (const p of programs) {
|
|
184
|
+
for (const d of p?.decls ?? []) {
|
|
185
|
+
if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body) {
|
|
186
|
+
const callees = findCalledFunctions(d.body);
|
|
187
|
+
addCallees(callGraph, d.name, callees);
|
|
188
|
+
if (d.kind === "MethodDecl" && d.recvType?.name) {
|
|
189
|
+
addCallees(callGraph, `${d.recvType.name}.${d.name}`, callees);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return callGraph;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildInitPathFunctions(programs) {
|
|
198
|
+
const seeds = buildInitSeeds(programs);
|
|
199
|
+
const callGraph = buildCallGraph(programs);
|
|
200
|
+
const initPath = new Set(seeds);
|
|
201
|
+
const worklist = Array.from(seeds);
|
|
202
|
+
while (worklist.length > 0) {
|
|
203
|
+
const caller = worklist.pop();
|
|
204
|
+
const callees = callGraph.get(caller);
|
|
205
|
+
if (callees) {
|
|
206
|
+
for (const callee of callees) {
|
|
207
|
+
if (!initPath.has(callee)) {
|
|
208
|
+
initPath.add(callee);
|
|
209
|
+
worklist.push(callee);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return initPath;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Idents read anywhere in the function, nested closures included: the emitter
|
|
218
|
+
// narrows the list per emitted body (root vs. each lifted closure).
|
|
219
|
+
function scanReadIdents(node, readSet) {
|
|
220
|
+
if (!node || typeof node !== "object") return;
|
|
221
|
+
if (Array.isArray(node)) {
|
|
222
|
+
for (const c of node) scanReadIdents(c, readSet);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (node.kind === "SelectorExpr") {
|
|
226
|
+
scanReadIdents(node.expr, readSet);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (node.kind === "DefineStmt") {
|
|
230
|
+
scanReadIdents(node.rhs, readSet);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (node.kind === "AssignStmt" || node.kind === "CompoundAssignStmt") {
|
|
234
|
+
for (const l of node.lhs ?? []) {
|
|
235
|
+
if (l.kind !== "Ident") scanReadIdents(l, readSet);
|
|
236
|
+
}
|
|
237
|
+
scanReadIdents(node.rhs, readSet);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (node.kind === "Ident" && typeof node.name === "string") {
|
|
241
|
+
readSet.add(node.name);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
forEachChild(node, (child) => scanReadIdents(child, readSet));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isLocallyDeclared(node, name) {
|
|
248
|
+
if (!node || typeof node !== "object") return false;
|
|
249
|
+
if (Array.isArray(node)) return node.some((c) => isLocallyDeclared(c, name));
|
|
250
|
+
if (node.kind === "FuncLit") return false;
|
|
251
|
+
if (node.kind === "DefineStmt") {
|
|
252
|
+
return Boolean(
|
|
253
|
+
node.lhs?.some((l) => l.kind === "Ident" && l.name === name),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (node.kind === "VarDecl") {
|
|
257
|
+
for (const s of node.decls ?? node.specs ?? []) {
|
|
258
|
+
const names = s.names ?? (s.name ? [s.name] : []);
|
|
259
|
+
if (names.some((n) => (typeof n === "string" ? n : n.name) === name))
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return someChild(node, (child) => isLocallyDeclared(child, name));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getGlobalsToCache(fn, neverAssignedGlobals, initPathFunctions) {
|
|
267
|
+
if (!fn.body || neverAssignedGlobals.size === 0) return [];
|
|
268
|
+
if (fn._isGlobalInit || fn.name === "init" || fn.name.startsWith("init$"))
|
|
269
|
+
return [];
|
|
270
|
+
if (initPathFunctions.has(fn.name)) return [];
|
|
271
|
+
if (fn._methodName && initPathFunctions.has(fn._methodName)) return [];
|
|
272
|
+
if (
|
|
273
|
+
fn.recvType?.name &&
|
|
274
|
+
initPathFunctions.has(`${fn.recvType.name}.${fn.name}`)
|
|
275
|
+
)
|
|
276
|
+
return [];
|
|
277
|
+
|
|
278
|
+
const paramNames = new Set((fn.params ?? []).map((p) => p.name));
|
|
279
|
+
if (fn.recvName) paramNames.add(fn.recvName);
|
|
280
|
+
|
|
281
|
+
const reads = new Set();
|
|
282
|
+
scanReadIdents(fn.body, reads);
|
|
283
|
+
|
|
284
|
+
const result = [];
|
|
285
|
+
for (const name of Array.from(neverAssignedGlobals).sort()) {
|
|
286
|
+
if (
|
|
287
|
+
reads.has(name) &&
|
|
288
|
+
!paramNames.has(name) &&
|
|
289
|
+
!isLocallyDeclared(fn.body, name)
|
|
290
|
+
) {
|
|
291
|
+
result.push(name);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function buildCachedGlobalsMap(
|
|
298
|
+
programs,
|
|
299
|
+
neverAssignedGlobals,
|
|
300
|
+
initPathFunctions,
|
|
301
|
+
) {
|
|
302
|
+
const map = new Map();
|
|
303
|
+
for (const p of programs) {
|
|
304
|
+
for (const d of p?.decls ?? []) {
|
|
305
|
+
if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body) {
|
|
306
|
+
const globals = getGlobalsToCache(
|
|
307
|
+
d,
|
|
308
|
+
neverAssignedGlobals,
|
|
309
|
+
initPathFunctions,
|
|
310
|
+
);
|
|
311
|
+
if (globals.length > 0) map.set(d, globals);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return map;
|
|
316
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/lower/index.js
|
|
2
|
+
// Lowering step: analyzes AST and types to populate side-tables for backends (JS and WASM).
|
|
3
|
+
|
|
4
|
+
export * from "./boxing.js";
|
|
5
|
+
export * from "./captures.js";
|
|
6
|
+
export * from "./embedding.js";
|
|
7
|
+
export * from "./escape.js";
|
|
8
|
+
export * from "./functions.js";
|
|
9
|
+
export * from "./globals.js";
|
|
10
|
+
export * from "./ownership.js";
|
|
11
|
+
export * from "./range.js";
|
|
12
|
+
export * from "./walk.js";
|
|
13
|
+
|
|
14
|
+
import { scanAddressTaken } from "./boxing.js";
|
|
15
|
+
import { analyzeCaptures } from "./captures.js";
|
|
16
|
+
import { computeEmbeddedStubs } from "./embedding.js";
|
|
17
|
+
import { paramEscapes } from "./escape.js";
|
|
18
|
+
import { extractNamedReturns, hasDefer, normalizeDefers } from "./functions.js";
|
|
19
|
+
import { computeGlobalAnalysis } from "./globals.js";
|
|
20
|
+
import { OwnershipContext } from "./ownership.js";
|
|
21
|
+
|
|
22
|
+
class LowerResult {
|
|
23
|
+
constructor() {
|
|
24
|
+
// Map<ASTNode (FuncDecl, MethodDecl, FuncLit), Set<string>>
|
|
25
|
+
this.boxedVars = new Map();
|
|
26
|
+
// Map<ASTNode, OwnershipContext>
|
|
27
|
+
this.ownership = new Map();
|
|
28
|
+
// Map<ASTNode, Object>
|
|
29
|
+
this.functions = new Map();
|
|
30
|
+
// Map<string, Array<{ methodName: string, embedName: string }>>
|
|
31
|
+
this.embeddedStubs = new Map();
|
|
32
|
+
// Map<ASTNode, CaptureAnalysis>
|
|
33
|
+
this.captures = new Map();
|
|
34
|
+
// Map<ASTNode, Set<string>>
|
|
35
|
+
this.escapes = new Map();
|
|
36
|
+
// Set<string>
|
|
37
|
+
this.neverAssignedGlobals = new Set();
|
|
38
|
+
// Set<string>
|
|
39
|
+
this.initPathFunctions = new Set();
|
|
40
|
+
// Map<ASTNode, Array<string>>
|
|
41
|
+
this.cachedGlobalsByFunc = new Map();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Runs lowering analyses across an AST or collection of programs, returning side-tables.
|
|
47
|
+
* @param {Object} programOrPrograms
|
|
48
|
+
* @param {Object} checker
|
|
49
|
+
* @returns {LowerResult}
|
|
50
|
+
*/
|
|
51
|
+
export function lower(programOrPrograms, checker = null) {
|
|
52
|
+
const res = new LowerResult();
|
|
53
|
+
const programs = Array.isArray(programOrPrograms)
|
|
54
|
+
? programOrPrograms
|
|
55
|
+
: [programOrPrograms];
|
|
56
|
+
|
|
57
|
+
normalizeDefers(programs);
|
|
58
|
+
|
|
59
|
+
// 1. Aggregate methods across all files in the package
|
|
60
|
+
const methodMap = new Map();
|
|
61
|
+
for (const program of programs) {
|
|
62
|
+
for (const d of program?.decls ?? []) {
|
|
63
|
+
if (d.kind === "MethodDecl" && d.recvType?.name) {
|
|
64
|
+
const recvTypeName = d.recvType.name;
|
|
65
|
+
if (!methodMap.has(recvTypeName)) methodMap.set(recvTypeName, []);
|
|
66
|
+
methodMap.get(recvTypeName).push(d);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Perform lowering passes
|
|
72
|
+
for (const program of programs) {
|
|
73
|
+
if (!program?.decls) continue;
|
|
74
|
+
|
|
75
|
+
for (const d of program.decls) {
|
|
76
|
+
if (d.kind === "FuncDecl" || d.kind === "MethodDecl") {
|
|
77
|
+
if (d.body) {
|
|
78
|
+
const boxed = scanAddressTaken(d.body, new Set());
|
|
79
|
+
res.boxedVars.set(d, boxed);
|
|
80
|
+
res.ownership.set(d, new OwnershipContext(d.body));
|
|
81
|
+
res.functions.set(d, {
|
|
82
|
+
hasDefer: hasDefer(d.body),
|
|
83
|
+
namedReturns: extractNamedReturns(d),
|
|
84
|
+
});
|
|
85
|
+
res.captures.set(d, analyzeCaptures(d));
|
|
86
|
+
|
|
87
|
+
const escapingParams = new Set();
|
|
88
|
+
for (const p of d.params ?? []) {
|
|
89
|
+
if (p.name && p.name !== "_" && paramEscapes(d.body, p.name)) {
|
|
90
|
+
escapingParams.add(p.name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
res.escapes.set(d, escapingParams);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Compute embedded stubs for structs
|
|
99
|
+
if (checker) {
|
|
100
|
+
for (const d of program.decls) {
|
|
101
|
+
if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
|
|
102
|
+
const methods = methodMap.get(d.name) ?? [];
|
|
103
|
+
const stubs = computeEmbeddedStubs(d.name, methods, checker);
|
|
104
|
+
res.embeddedStubs.set(d.name, stubs);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. Compute package-level global analysis
|
|
111
|
+
const { neverAssignedGlobals, initPathFunctions, cachedGlobalsByFunc } =
|
|
112
|
+
computeGlobalAnalysis(programs, checker);
|
|
113
|
+
res.neverAssignedGlobals = neverAssignedGlobals;
|
|
114
|
+
res.initPathFunctions = initPathFunctions;
|
|
115
|
+
res.cachedGlobalsByFunc = cachedGlobalsByFunc;
|
|
116
|
+
|
|
117
|
+
return res;
|
|
118
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/lower/ownership.js
|
|
2
|
+
// Tracks variable ownership, mutation and clone elision for Go value semantics.
|
|
3
|
+
|
|
4
|
+
import { forEachChild, someChild } from "./walk.js";
|
|
5
|
+
|
|
6
|
+
export function rootIdentName(e) {
|
|
7
|
+
while (e) {
|
|
8
|
+
if (e.kind === "Ident") return e.name;
|
|
9
|
+
if (e.kind === "SelectorExpr" || e.kind === "IndexExpr") e = e.expr;
|
|
10
|
+
else if (e.kind === "UnaryExpr" && e.op === "*") e = e.operand;
|
|
11
|
+
else return null;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// True when `node` itself (not its children) writes to variable `name`.
|
|
17
|
+
export function nodeWritesVar(node, name) {
|
|
18
|
+
const root = (e) => rootIdentName(e) === name;
|
|
19
|
+
switch (node.kind) {
|
|
20
|
+
case "AssignStmt":
|
|
21
|
+
return node.lhs.some(root);
|
|
22
|
+
case "DefineStmt":
|
|
23
|
+
return node.lhs.some((e) => e._redecl && e.name === name);
|
|
24
|
+
case "IncDecStmt":
|
|
25
|
+
return root(node.expr);
|
|
26
|
+
case "SelectorExpr":
|
|
27
|
+
return Boolean(
|
|
28
|
+
node._isMethodValue && node._type?._ptrRecv && root(node.expr),
|
|
29
|
+
);
|
|
30
|
+
default:
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// True when `node` may modify (or take the address of) the value held by variable `name`.
|
|
36
|
+
export function nodeMutatesVar(node, name, addrOnly = false) {
|
|
37
|
+
if (!node || typeof node !== "object") return false;
|
|
38
|
+
if (Array.isArray(node))
|
|
39
|
+
return node.some((n) => nodeMutatesVar(n, name, addrOnly));
|
|
40
|
+
if (
|
|
41
|
+
node.kind === "UnaryExpr" &&
|
|
42
|
+
node.op === "&" &&
|
|
43
|
+
rootIdentName(node.operand) === name
|
|
44
|
+
)
|
|
45
|
+
return true;
|
|
46
|
+
if (!addrOnly && nodeWritesVar(node, name)) return true;
|
|
47
|
+
return someChild(node, (child) => nodeMutatesVar(child, name, addrOnly));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Single-pass collector of all mutated or address-taken variables in an AST node.
|
|
51
|
+
export function collectMutatedVars(
|
|
52
|
+
node,
|
|
53
|
+
mutated = new Set(),
|
|
54
|
+
addrOnly = false,
|
|
55
|
+
) {
|
|
56
|
+
if (!node || typeof node !== "object") return mutated;
|
|
57
|
+
if (Array.isArray(node)) {
|
|
58
|
+
for (const item of node) collectMutatedVars(item, mutated, addrOnly);
|
|
59
|
+
return mutated;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (node.kind === "UnaryExpr" && node.op === "&") {
|
|
63
|
+
const root = rootIdentName(node.operand);
|
|
64
|
+
if (root) mutated.add(root);
|
|
65
|
+
} else if (!addrOnly) {
|
|
66
|
+
if (node.kind === "AssignStmt") {
|
|
67
|
+
for (const e of node.lhs) {
|
|
68
|
+
const root = rootIdentName(e);
|
|
69
|
+
if (root) mutated.add(root);
|
|
70
|
+
}
|
|
71
|
+
} else if (node.kind === "DefineStmt") {
|
|
72
|
+
for (const e of node.lhs) {
|
|
73
|
+
if (e._redecl && e.name) mutated.add(e.name);
|
|
74
|
+
}
|
|
75
|
+
} else if (node.kind === "IncDecStmt") {
|
|
76
|
+
const root = rootIdentName(node.expr);
|
|
77
|
+
if (root) mutated.add(root);
|
|
78
|
+
} else if (node.kind === "SelectorExpr") {
|
|
79
|
+
if (node._isMethodValue && node._type?._ptrRecv) {
|
|
80
|
+
const root = rootIdentName(node.expr);
|
|
81
|
+
if (root) mutated.add(root);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
forEachChild(node, (child) => collectMutatedVars(child, mutated, addrOnly));
|
|
87
|
+
return mutated;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function fnMutates(body, name, addrOnly = false, cache = null) {
|
|
91
|
+
if (!body) return true;
|
|
92
|
+
const key = `${addrOnly ? "&" : ""}${name}`;
|
|
93
|
+
if (cache) {
|
|
94
|
+
if (!cache.has(key)) {
|
|
95
|
+
cache.set(key, nodeMutatesVar(body, name, addrOnly));
|
|
96
|
+
}
|
|
97
|
+
return cache.get(key);
|
|
98
|
+
}
|
|
99
|
+
return nodeMutatesVar(body, name, addrOnly);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function closureMutates(body, name, cache = null) {
|
|
103
|
+
const key = `λ${name}`;
|
|
104
|
+
if (cache?.has(key)) return cache.get(key);
|
|
105
|
+
|
|
106
|
+
const walk = (node) => {
|
|
107
|
+
if (!node || typeof node !== "object") return false;
|
|
108
|
+
if (Array.isArray(node)) return node.some(walk);
|
|
109
|
+
if (node.kind === "FuncLit") return nodeMutatesVar(node.body, name);
|
|
110
|
+
return someChild(node, walk);
|
|
111
|
+
};
|
|
112
|
+
const res = walk(body);
|
|
113
|
+
if (cache) cache.set(key, res);
|
|
114
|
+
return res;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class OwnershipContext {
|
|
118
|
+
constructor(body) {
|
|
119
|
+
this.body = body;
|
|
120
|
+
this.mut = new Map();
|
|
121
|
+
this.owned = new Set();
|
|
122
|
+
this.borrowed = new Set();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
markOwnership(name, owned) {
|
|
126
|
+
if (!name || name === "_") return;
|
|
127
|
+
(owned ? this.owned : this.borrowed).add(name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fnMutates(name, addrOnly = false) {
|
|
131
|
+
return fnMutates(this.body, name, addrOnly, this.mut);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
closureMutates(name) {
|
|
135
|
+
return closureMutates(this.body, name, this.mut);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/lower/range.js
|
|
2
|
+
// Classifies for-range loop shapes and variable scoping/redelcarations.
|
|
3
|
+
|
|
4
|
+
import { isNumeric } from "../typechecker/types.js";
|
|
5
|
+
import { someChild } from "./walk.js";
|
|
6
|
+
|
|
7
|
+
export function isRangeFor(stmt) {
|
|
8
|
+
if (!stmt?.init) return false;
|
|
9
|
+
const init = stmt.init;
|
|
10
|
+
if (init.kind !== "DefineStmt" && init.kind !== "AssignStmt") return false;
|
|
11
|
+
return init.rhs?.[0]?.kind === "RangeExpr";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isIntRangeType(iterType) {
|
|
15
|
+
return isNumeric(iterType);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isMapRangeType(iterType) {
|
|
19
|
+
return (
|
|
20
|
+
iterType?.kind === "map" ||
|
|
21
|
+
(iterType?.kind === "named" && iterType.underlying?.kind === "map")
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isStringRangeType(iterType) {
|
|
26
|
+
return (
|
|
27
|
+
(iterType?.kind === "basic" && iterType.name === "string") ||
|
|
28
|
+
(iterType?.kind === "untyped" && iterType.base === "string")
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// True when `node` contains an assignment or ++/-- targeting identifier `name`.
|
|
33
|
+
export function nodeAssigns(node, name) {
|
|
34
|
+
if (!node || typeof node !== "object") return false;
|
|
35
|
+
if (Array.isArray(node)) return node.some((n) => nodeAssigns(n, name));
|
|
36
|
+
if (
|
|
37
|
+
(node.kind === "AssignStmt" &&
|
|
38
|
+
node.lhs.some((e) => e.kind === "Ident" && e.name === name)) ||
|
|
39
|
+
(node.kind === "IncDecStmt" &&
|
|
40
|
+
node.expr.kind === "Ident" &&
|
|
41
|
+
node.expr.name === name)
|
|
42
|
+
)
|
|
43
|
+
return true;
|
|
44
|
+
return someChild(node, (child) => nodeAssigns(child, name));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// True when the body re-declares one of `names` at its top level (legal Go shadowing).
|
|
48
|
+
export function bodyRedeclares(body, names) {
|
|
49
|
+
if (!names || names.length === 0) return false;
|
|
50
|
+
for (const s of body?.stmts ?? []) {
|
|
51
|
+
if (s.kind === "DefineStmt" && s.lhs.some((e) => names.includes(e.name)))
|
|
52
|
+
return true;
|
|
53
|
+
if (
|
|
54
|
+
(s.kind === "VarDecl" || s.kind === "ConstDecl") &&
|
|
55
|
+
s.decls?.some((d) => d.names?.some((n) => names.includes(n)))
|
|
56
|
+
)
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Whether `for i := range n` can be emitted as a plain `for (let i = 0; …)` without hidden registers.
|
|
63
|
+
export function isSimpleIntRange(stmt, name, isAssign, boxedVars = new Set()) {
|
|
64
|
+
if (isAssign) return false;
|
|
65
|
+
if (!name || name === "_") return true;
|
|
66
|
+
return !boxedVars.has(name) && !nodeAssigns(stmt.body, name);
|
|
67
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/lower/walk.js
|
|
2
|
+
// Shared AST child traversal for the lowering passes. Side-table fields
|
|
3
|
+
// (`_type`, `_boxed`, …) start with "_" and are never AST children.
|
|
4
|
+
|
|
5
|
+
/** True for a side-table key that traversal must skip. */
|
|
6
|
+
function isSideTableKey(key) {
|
|
7
|
+
return key.charCodeAt(0) === 95; // "_"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Calls `fn(child)` for every non-side-table property of `node`. */
|
|
11
|
+
export function forEachChild(node, fn) {
|
|
12
|
+
for (const key of Object.keys(node)) {
|
|
13
|
+
if (isSideTableKey(key)) continue;
|
|
14
|
+
fn(node[key]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** True when `pred(child)` holds for some non-side-table property of `node`. */
|
|
19
|
+
export function someChild(node, pred) {
|
|
20
|
+
for (const key of Object.keys(node)) {
|
|
21
|
+
if (isSideTableKey(key)) continue;
|
|
22
|
+
if (pred(node[key])) return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
package/src/minifier.js
CHANGED
package/src/parser/index.js
CHANGED
|
@@ -114,6 +114,8 @@ export class Parser {
|
|
|
114
114
|
pkg,
|
|
115
115
|
imports,
|
|
116
116
|
decls,
|
|
117
|
+
target: this.tokens?.target ?? null,
|
|
118
|
+
_targetDirective: this.tokens?.targetDirective ?? null,
|
|
117
119
|
_filename: this.filename,
|
|
118
120
|
_source: this.source,
|
|
119
121
|
};
|
|
@@ -130,7 +132,9 @@ export class Parser {
|
|
|
130
132
|
this.expect(T.IMPORT);
|
|
131
133
|
const imports = [];
|
|
132
134
|
const parseOne = () => {
|
|
133
|
-
const
|
|
135
|
+
const tok = this.peek();
|
|
136
|
+
const _line = tok.line;
|
|
137
|
+
const _col = tok.col;
|
|
134
138
|
let alias = null;
|
|
135
139
|
if (this.check(T.IDENT)) {
|
|
136
140
|
alias = this.advance().value;
|
|
@@ -138,8 +142,9 @@ export class Parser {
|
|
|
138
142
|
alias = ".";
|
|
139
143
|
this.advance();
|
|
140
144
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
145
|
+
const pathTok = this.expect(T.STRING);
|
|
146
|
+
const path = pathTok.value;
|
|
147
|
+
return { path, alias, _line, _col, line: pathTok.line, col: pathTok.col };
|
|
143
148
|
};
|
|
144
149
|
if (this.match(T.LPAREN)) {
|
|
145
150
|
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|