@asmlift/core 0.9.0 → 0.10.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.
Files changed (101) hide show
  1. package/README.md +25 -24
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +149 -0
  4. package/src/backend/c.ts +1 -0
  5. package/src/backend/cfamily.ts +165 -50
  6. package/src/backend/cpp.ts +2 -1
  7. package/src/backend/pascal.ts +31 -12
  8. package/src/contracts.ts +173 -16
  9. package/src/declare.ts +69 -20
  10. package/src/frontend/asmdata.ts +7 -1
  11. package/src/frontend/call-plan.ts +964 -60
  12. package/src/frontend/dead-reads.ts +628 -0
  13. package/src/frontend/device-pins.ts +23 -4
  14. package/src/frontend/emit.ts +4 -3
  15. package/src/frontend/fpu.ts +224 -17
  16. package/src/frontend/frame-objects.ts +1232 -192
  17. package/src/frontend/frontend.ts +64 -3
  18. package/src/frontend/high-half.ts +53 -4
  19. package/src/frontend/jump-table.ts +530 -0
  20. package/src/frontend/local-object.ts +158 -32
  21. package/src/frontend/mips.ts +2076 -215
  22. package/src/frontend/own-struct-return.ts +500 -0
  23. package/src/frontend/ppc.ts +2711 -232
  24. package/src/frontend/register-struct-call.ts +484 -0
  25. package/src/frontend/reloc-symbol.ts +22 -7
  26. package/src/frontend/ssa.ts +1460 -44
  27. package/src/frontend/stackargs.ts +106 -57
  28. package/src/frontend/thumb.ts +2044 -318
  29. package/src/ir/alias.ts +42 -0
  30. package/src/ir/bits.ts +681 -1
  31. package/src/ir/core.ts +100 -5
  32. package/src/ir/discipline.ts +86 -21
  33. package/src/ir/entry.ts +159 -0
  34. package/src/{structure → ir}/loops.ts +6 -2
  35. package/src/ir/opcodes.ts +109 -9
  36. package/src/ir/types.ts +86 -2
  37. package/src/ir/verify.ts +80 -11
  38. package/src/l3/ast.ts +290 -18
  39. package/src/l3/basecse.ts +33 -5
  40. package/src/l3/constinit.ts +382 -0
  41. package/src/l3/dce.ts +2 -2
  42. package/src/l3/initfirst.ts +42 -11
  43. package/src/l3/intcell.ts +2 -1
  44. package/src/l3/narrowdecl.ts +77 -36
  45. package/src/l3/pollguard.ts +8 -2
  46. package/src/l3/ptrcell.ts +31 -34
  47. package/src/l3/ptrfield.ts +123 -8
  48. package/src/l3/regspell.ts +9 -4
  49. package/src/l3/reindex.ts +283 -96
  50. package/src/l3/storage.ts +98 -1
  51. package/src/l3/symbol-refs.ts +84 -6
  52. package/src/l3/tailmerge.ts +4 -2
  53. package/src/l3/typing.ts +108 -15
  54. package/src/l3/unreduce.ts +6 -5
  55. package/src/l3/volatileptr.ts +17 -54
  56. package/src/mangle.ts +30 -0
  57. package/src/pattern/engine.ts +361 -73
  58. package/src/pipeline.ts +541 -24
  59. package/src/proto-context.ts +905 -45
  60. package/src/proto.ts +555 -40
  61. package/src/raise/aggmembers.ts +290 -0
  62. package/src/raise/arrays.ts +5 -1
  63. package/src/raise/declared-volatile.ts +207 -31
  64. package/src/raise/floathelpers.ts +254 -33
  65. package/src/raise/gvn.ts +3 -3
  66. package/src/raise/latch.ts +162 -6
  67. package/src/raise/memberarrays.ts +10 -4
  68. package/src/raise/offsetnames.ts +10 -26
  69. package/src/raise/pairparams.ts +33 -0
  70. package/src/raise/paramwidth.ts +196 -6
  71. package/src/raise/pre-recovery.ts +102 -19
  72. package/src/raise/pre-structure.ts +54 -0
  73. package/src/raise/recover.ts +12 -5
  74. package/src/raise/reroll.ts +694 -0
  75. package/src/raise/shortcircuit.ts +26 -12
  76. package/src/raise/softdiv.ts +1 -1
  77. package/src/raise/storesign.ts +153 -0
  78. package/src/raise/struct-arrays.ts +30 -11
  79. package/src/raise/structs.ts +396 -33
  80. package/src/raise/truncload.ts +4 -3
  81. package/src/raise/unthread.ts +400 -0
  82. package/src/rank-declare.ts +118 -12
  83. package/src/rank-variations.ts +89 -31
  84. package/src/rank.ts +307 -54
  85. package/src/runtime-helpers.ts +140 -22
  86. package/src/structure/analysis.ts +165 -2
  87. package/src/structure/arith-ops.ts +23 -0
  88. package/src/structure/globalaccess.ts +111 -9
  89. package/src/structure/hazards.ts +16 -64
  90. package/src/structure/local-statics.ts +145 -9
  91. package/src/structure/namecoalesce.ts +1 -1
  92. package/src/structure/pointer-spelling.ts +1106 -0
  93. package/src/structure/redundant-test.ts +1 -1
  94. package/src/structure/structure.ts +2180 -1054
  95. package/src/structure/switch-recover.ts +244 -100
  96. package/src/symbols.ts +6 -0
  97. package/src/target.ts +765 -52
  98. package/src/trace.ts +26 -8
  99. package/src/variation-definitions.ts +126 -14
  100. package/src/variation-gates.ts +2 -0
  101. package/src/variation-tokens.ts +5 -1
@@ -4,9 +4,9 @@
4
4
  // • may this loop's updates be emitted before its condition/exit/post-loop reads, or would some
5
5
  // read then see a clobbered (post-update) value that the original IR read PRE-update — and
6
6
  // which of those reads can be REPAIRED by moving the copy into the body (`sinkable…`);
7
- // • is a value the emitted form DROPS redundant — `sameAtEntry`, which reads an expression on
8
- // the loop's entry state so a guard about to be fused away can be checked against the test
9
- // that replaces it.
7
+ // • is a value the emitted form DROPS redundant — `sameAtEntry` (ir/entry.ts, shared with the
8
+ // raise re-roll), which reads an expression on the loop's entry state so a guard about to be
9
+ // fused away can be checked against the test that replaces it.
10
10
  //
11
11
  // WHAT MAKES A SUNK COPY LEGAL. The exit edge's value is not moved, it is REBUILT: the copy spells
12
12
  // the arg's def-tree again inside the body, at the point that tree was already computed at
@@ -50,7 +50,7 @@
50
50
  // exist at CALL time (emission runs after naming completes). Snapshotting them would break this.
51
51
  import { Block, Op, Value, successorsOf } from '../ir/core';
52
52
  import { effectful, orderSensitive, reevalUnsafe } from '../ir/discipline';
53
- import { NEGATED_ICMP } from '../ir/opcodes';
53
+ import { sameAtEntry } from '../ir/entry';
54
54
  import { Expr, Stmt } from '../l3/ast';
55
55
  import { type Gate, firstRejection } from '../l3/gates';
56
56
  import type { UseSite } from './analysis';
@@ -120,7 +120,15 @@ export interface LoopHazards {
120
120
  gates?: readonly Gate<SinkCandidate>[],
121
121
  staleOnZeroTrip?: (slot: number) => boolean,
122
122
  ): Map<number, Op | null>;
123
- sameAtEntry(a: Value, b: Value, entry: Map<Value, Value>, negated?: boolean): boolean;
123
+ /** `through`: the values whose defining op may be read through (ir/entry.ts `EntryReading`);
124
+ * every value when absent. */
125
+ sameAtEntry(
126
+ a: Value,
127
+ b: Value,
128
+ entry: Map<Value, Value>,
129
+ negated?: boolean,
130
+ through?: (v: Value) => boolean,
131
+ ): boolean;
124
132
  loopWriteSet(updates: Stmt[], bodyBlocks: Iterable<Block>, header: Block): Set<string>;
125
133
  loopWriteSetAhead(ahead: Iterable<Block>, incoming: (b: Block) => Value[][], header: Block): Set<string>;
126
134
  }
@@ -324,7 +332,7 @@ interface Reach {
324
332
  const ALWAYS: Reach = { onTrue: true, onFalse: true };
325
333
 
326
334
  /** The reach each operand of a SHORT-CIRCUIT op inherits. These two are the whole set: they are the
327
- * ops `ARITH_TO_BIN` (structure.ts) renders as `&&`/`||` — a test holds the two lists together,
335
+ * ops `ARITH_TO_BIN` (arith-ops.ts) renders as `&&`/`||` — a test holds the two lists together,
328
336
  * since a connective missing from here is read as an ordinary op and its arms inherit a reach they
329
337
  * do not have — and `Expr` has no conditional form besides them, no ternary, so every other op
330
338
  * evaluates all of its operands whenever it is itself evaluated and they inherit its reach
@@ -363,63 +371,6 @@ function unitStep(value: Expr, name: string): 1 | -1 | null {
363
371
  return k === null ? null : ((value.op === '-' ? -k : k) as 1 | -1);
364
372
  }
365
373
 
366
- // `x + 0` / `x - 0` / `x | 0` are `x`. Substituting a loop variable by its init constant turns
367
- // ordinary index arithmetic into exactly these, and a guard that spells the same value without
368
- // the arithmetic would otherwise compare unequal.
369
- function fold(defs: Map<Value, Op>, v: Value): Value {
370
- const d = defs.get(v);
371
- if (!d || d.operands.length !== 2 || !['add', 'sub', 'or'].includes(d.opcode)) {
372
- return v;
373
- }
374
- const z = defs.get(d.operands[1]);
375
- return z?.opcode === 'const' && z.attrs?.value === 0 ? fold(defs, d.operands[0]) : v;
376
- }
377
-
378
- // Does `a`, read on a loop's ENTRY values, denote the same thing as `b`? `entry` maps each header
379
- // param and back-edge arg to the arg the forward edge passes, so substituting through it models
380
- // the FIRST iteration — the state a guard in front of the loop tested. `negated` compares against
381
- // b's logical opposite instead, for the usual case where a guard spells the loop's own test the
382
- // other way round (`beq` to the exit vs `bne` to the header).
383
- //
384
- // Structural, not semantic: distinct ops with the same opcode, attributes and operands compare
385
- // equal (two `const 0`s do), anything else does not. A false negative costs a loud decline, which
386
- // is the direction to be wrong in. Memoised like `readsClobbered`'s `seen`: a value its own
387
- // consumer reads twice would otherwise double the work at every level.
388
- function sameAtEntry(defs: Map<Value, Op>, a: Value, b: Value, entry: Map<Value, Value>, negated = false): boolean {
389
- const memo = new Map<Value, Map<Value, boolean>>();
390
- const sameOp = (da: Op, db: Op, opcodeOk: boolean): boolean =>
391
- opcodeOk &&
392
- da.operands.length === db.operands.length &&
393
- JSON.stringify(da.attrs ?? null) === JSON.stringify(db.attrs ?? null) &&
394
- da.operands.every((o, i) => same(o, db.operands[i]));
395
- const same = (x0: Value, y0: Value): boolean => {
396
- const x = fold(defs, entry.get(x0) ?? x0);
397
- const y = fold(defs, y0);
398
- if (x === y) {
399
- return true;
400
- }
401
- let row = memo.get(x);
402
- if (!row) {
403
- memo.set(x, (row = new Map()));
404
- }
405
- const hit = row.get(y);
406
- if (hit !== undefined) {
407
- return hit;
408
- }
409
- const da = defs.get(x);
410
- const db = defs.get(y);
411
- const r = !!da && !!db && sameOp(da, db, da.opcode === db.opcode);
412
- row.set(y, r);
413
- return r;
414
- };
415
- if (!negated) {
416
- return same(a, b);
417
- }
418
- const da = defs.get(fold(defs, entry.get(a) ?? a));
419
- const db = defs.get(fold(defs, b));
420
- return !!da && !!db && sameOp(da, db, NEGATED_ICMP[da.opcode] === db.opcode);
421
- }
422
-
423
374
  // WHERE A SUNK COPY IS REBUILT. The copy is not carried into the body, it is SPELLED AGAIN
424
375
  // there, so the point it belongs at is the one its value was computed at: the arg's own defining
425
376
  // op, when that op is one of `latch`'s — the block whose statements both loop emitters render
@@ -1068,7 +1019,8 @@ export function makeLoopHazards(deps: LoopHazardDeps): LoopHazards {
1068
1019
  testSkipsAnEffect,
1069
1020
  preUpdateCondFold,
1070
1021
  sinkablePreUpdateSlots,
1071
- sameAtEntry: (a, b, entry, negated = false) => sameAtEntry(defs, a, b, entry, negated),
1022
+ sameAtEntry: (a, b, entry, negated = false, through = undefined) =>
1023
+ sameAtEntry(defs, a, b, through ? { entry, through } : { entry }, negated),
1072
1024
  loopWriteSet,
1073
1025
  loopWriteSetAhead,
1074
1026
  };
@@ -29,20 +29,38 @@
29
29
  // source's (`tide`) in one step, `nameLocalStatics`, which is where a clash between that name and
30
30
  // anything else the function names can first be seen whole.
31
31
  //
32
+ // An element the target RELOCATES holds an address — a table of function pointers, of data
33
+ // pointers, of `&arr[k]` — and is spelled as the address constant it is, `(u32)&sym` or
34
+ // `(u32)((u8 *)&sym + k)`, in the integer element the rest of this typing picks: such an element
35
+ // compiles to the same word and the same relocation as the pointer-typed source (agbcc and mwcc,
36
+ // compiled), so the definition needs no pointer type to put the bytes where the target has them.
37
+ //
32
38
  // REFUSES, naming the static, when the target's compiler declares no layout rules, when nothing
33
39
  // in the definition settles the width and the accesses disagree on it, when the width is no
34
40
  // integer type's or does not divide the size, when the loads disagree on the signedness, when the
35
41
  // definition is aligned narrower than its declaration here would be, when a bss static sits where
36
42
  // only a scalar initialized to zero is sure to land and it is no scalar (or the input does not
37
- // show where it sits);
43
+ // show where it sits), when an element holding an address — a static's or the compiler's
44
+ // anonymous image's — is no pointer's width;
38
45
  // and, at the rename, when two statics share a source name (one block cannot declare both) or
39
46
  // when the function names anything else by it — a global, a callee, a parameter, a local, itself —
40
- // which the block-scope static would hide.
41
- import { type Fn, type LocalObject, type LocalObjects, type Op, type Value, defOpMap } from '../ir/core';
47
+ // which the block-scope static would hide — and when a global an initializer holds the address of
48
+ // is hidden there by a static, parameter or local of its name.
49
+ import {
50
+ type Fn,
51
+ LOCAL_OBJECT_ADDRESS_BYTES,
52
+ type LocalObject,
53
+ type LocalObjects,
54
+ type Op,
55
+ type Value,
56
+ defOpMap,
57
+ } from '../ir/core';
42
58
  import { type IrType, T } from '../ir/types';
43
59
  import {
44
60
  type Expr,
61
+ type InitAddress,
45
62
  type SFn,
63
+ type SImage,
46
64
  type SStatic,
47
65
  type Stmt,
48
66
  mapExprChildren,
@@ -123,6 +141,8 @@ function zeroInitialized(
123
141
  export interface LocalStaticShapes {
124
142
  infos: Map<string, SymbolInfo>;
125
143
  statics: SStatic[];
144
+ /** the compiler's anonymous constant images, which no declaration spells (l3/constinit.ts) */
145
+ images: SImage[];
126
146
  }
127
147
 
128
148
  /** One access through a static's address: the width it reads or writes, and a load's extension. */
@@ -206,8 +226,11 @@ function elements(obj: LocalObject, width: number, signed: boolean): number[] {
206
226
  }
207
227
 
208
228
  /** The width of `obj`'s element: the one its definition's directives write, else the one every
209
- * access agrees on; with no access either, the whole object where it is aligned below the
210
- * compiler's aggregate floor, as only a scalar is, and a byte where it is not. */
229
+ * access agrees on; with no access either, a pointer's where the target relocates a word of it (an
230
+ * address table only handed to a callee, on a compiler that writes no directives: mwcc), else the
231
+ * whole object where it is aligned below the compiler's aggregate floor, as only a scalar is, and
232
+ * a byte where it is not. A relocation the pointer width does not seat is still refused, by the
233
+ * caller's check of every relocation's offset. */
211
234
  function elementWidth(
212
235
  obj: LocalObject,
213
236
  accesses: readonly Access[],
@@ -220,9 +243,15 @@ function elementWidth(
220
243
  if (widths.length > 1) {
221
244
  return { refused: `whose accesses disagree on its element width (${widths.sort().join(' and ')} bytes)` };
222
245
  }
246
+ if (widths.length === 1) {
247
+ return widths[0];
248
+ }
249
+ if ((obj.relocs?.length ?? 0) > 0 && obj.size % LOCAL_OBJECT_ADDRESS_BYTES === 0) {
250
+ return LOCAL_OBJECT_ADDRESS_BYTES;
251
+ }
223
252
  const shown = obj.directives?.align ?? obj.placement?.align;
224
253
  const scalarOnly = shown !== undefined && shown < layout.aggregateAlign;
225
- return widths[0] ?? (scalarOnly && [1, 2, 4].includes(obj.size) ? obj.size : 1);
254
+ return scalarOnly && [1, 2, 4].includes(obj.size) ? obj.size : 1;
226
255
  }
227
256
 
228
257
  /** The shape and definition of every static `fn` defines, or the refusal: the static's linker name
@@ -234,8 +263,9 @@ export function localStaticShapes(
234
263
  const objs = fn.localObjects;
235
264
  const infos = new Map<string, SymbolInfo>();
236
265
  const statics: SStatic[] = [];
266
+ const images: SImage[] = [];
237
267
  if (objs === undefined) {
238
- return { infos, statics };
268
+ return { infos, statics, images };
239
269
  }
240
270
  if (layout === undefined) {
241
271
  const [first] = objs.keys();
@@ -262,6 +292,75 @@ export function localStaticShapes(
262
292
  // it places in the order it first uses them: the candidate's code decides that, no declaration.
263
293
  for (const obj of [...objs.values()].sort((a, b) => a.order - b.order)) {
264
294
  const say = (why: string) => ({ symbol: obj.symbol, refused: why });
295
+ if (obj.anonymous) {
296
+ // The compiler's own image of a local aggregate's initializer: typed by its directives alone,
297
+ // since nothing but the copy reads it, and never declared where it sits — l3/constinit.ts
298
+ // folds it into the local the copy fills, which is the declaration that placed it.
299
+ const unit = obj.directives?.unit;
300
+ if (unit === undefined || obj.bytes === undefined) {
301
+ return say('whose image writes no one element width — a struct, or padding alone');
302
+ }
303
+ if (![1, 2, 4].includes(unit) || obj.size % unit !== 0) {
304
+ return say(`whose ${unit}-byte elements are no integer array of its ${obj.size} bytes`);
305
+ }
306
+ const signed = obj.directives!.negative;
307
+ const count = obj.size / unit;
308
+ infos.set(obj.symbol, {
309
+ name: obj.symbol,
310
+ kind: 'data',
311
+ size: obj.size,
312
+ shape: 'array',
313
+ elemSize: unit,
314
+ elemSigned: signed,
315
+ dims: [count],
316
+ const: true,
317
+ });
318
+ // Only the elements the source spelled: an element it left out is a hole, and the holes after
319
+ // the last value are dropped. Spelled out as zeros, the initializer is a different one to the
320
+ // compiler, which clears and stores where most of one is zero rather than copy an image
321
+ // (expr.c `mostly_zeros_p`) — so the printer spells the element after a hole by designator,
322
+ // where the compiler parses one (`SFn.designators`).
323
+ const init: (number | InitAddress | null)[] = elements(obj, unit, signed);
324
+ for (const [from, n] of obj.directives!.gaps ?? []) {
325
+ if (from % unit !== 0 || n % unit !== 0) {
326
+ return say(`whose padding at byte ${from} does not fall on its ${unit}-byte elements`);
327
+ }
328
+ init.fill(null, from / unit, (from + n) / unit);
329
+ }
330
+ // An element the image relocates holds an address, as a static's does — an array of pointers
331
+ // (agbcc copies one with memcpy from 16 words, compiled).
332
+ for (const r of obj.relocs ?? []) {
333
+ if (unit !== LOCAL_OBJECT_ADDRESS_BYTES || r.offset % unit !== 0) {
334
+ return say(
335
+ `whose image holds the address '${r.symbol}', which its ${unit}-byte elements cannot — ` +
336
+ `an address is ${LOCAL_OBJECT_ADDRESS_BYTES}`,
337
+ );
338
+ }
339
+ init[r.offset / unit] = { symbol: r.symbol, addend: r.addend };
340
+ }
341
+ // A string literal's image is placed at the compiler's string alignment, which a brace list of
342
+ // strings is not (agbcc: `.align 2, 0` ahead of a STRING_CST's label, none ahead of
343
+ // `{"abcde"}`'s) — the same bytes, but spelled as a literal the image moves to a stricter
344
+ // alignment and shifts the unit's `.rodata`. Nor has it a padding run: one shorter than its
345
+ // array is copied at the literal's length and the tail cleared (`char a[16] = "abc…hijklm"` is
346
+ // memcpy 15 + memset 1), so a `.space` after the `.ascii` is a brace list's image copied whole.
347
+ // So the test is asked before the trailing holes are dropped, not after.
348
+ const string =
349
+ obj.directives!.string === true &&
350
+ unit === 1 &&
351
+ obj.directives!.align >= layout.stringAlign &&
352
+ !init.includes(null);
353
+ while (init.length > 0 && init[init.length - 1] === null) {
354
+ init.pop();
355
+ }
356
+ images.push({
357
+ name: obj.symbol,
358
+ type: T.array(T.int(unit * 8, signed), count),
359
+ init,
360
+ ...(string ? { string: true as const } : {}),
361
+ });
362
+ continue;
363
+ }
265
364
  const accesses = access.get(obj.symbol) ?? [];
266
365
  const dir = obj.directives;
267
366
  // the alignment the target shows
@@ -284,7 +383,22 @@ export function localStaticShapes(
284
383
  const signed = signs.size > 0 ? signs.has(true) : dir?.negative === true;
285
384
  const count = obj.size / width;
286
385
  const elem = T.int(width * 8, signed);
287
- const init = zeroed.has(obj.symbol) ? [0] : obj.bytes === undefined ? undefined : elements(obj, width, signed);
386
+ const init: (number | InitAddress)[] | undefined = zeroed.has(obj.symbol)
387
+ ? [0]
388
+ : obj.bytes === undefined
389
+ ? undefined
390
+ : elements(obj, width, signed);
391
+ // An element the target relocates holds an address, which only an element of a pointer's width
392
+ // can: the relocation fills exactly that many bytes.
393
+ for (const r of obj.relocs ?? []) {
394
+ if (width !== LOCAL_OBJECT_ADDRESS_BYTES || r.offset % width !== 0) {
395
+ return say(
396
+ `whose initializer holds the address '${r.symbol}', which its ${width}-byte elements cannot — ` +
397
+ `an address is ${LOCAL_OBJECT_ADDRESS_BYTES}`,
398
+ );
399
+ }
400
+ init![r.offset / width] = { symbol: r.symbol, addend: r.addend };
401
+ }
288
402
  if (zeroed.has(obj.symbol) && count !== 1) {
289
403
  return say(`laid out where only a scalar initialized to zero is, but of ${count} elements`);
290
404
  }
@@ -320,7 +434,7 @@ export function localStaticShapes(
320
434
  ...(string ? { string: true as const } : {}),
321
435
  });
322
436
  }
323
- return { infos, statics };
437
+ return { infos, statics, images };
324
438
  }
325
439
 
326
440
  /** `sfn` with each static's linker name replaced by its source name — every reference, assignment
@@ -332,7 +446,26 @@ export function nameLocalStatics(sfn: SFn, objs: LocalObjects): SFn | { symbol:
332
446
  for (const g of sfn.globals ?? []) {
333
447
  taken.add(g.name);
334
448
  }
449
+ // The globals an initializer holds the address of are named here as much as the body's: a static
450
+ // of the same name is in scope from its own declarator on, its initializer included.
451
+ const addressed = new Map<string, string>();
452
+ for (const obj of objs.values()) {
453
+ for (const r of obj.relocs ?? []) {
454
+ taken.add(r.symbol);
455
+ addressed.set(r.symbol, obj.symbol);
456
+ }
457
+ }
335
458
  const bound = new Set([...sfn.params, ...sfn.locals].map((x) => x.name));
459
+ // …and the function's own parameters hide one too: a static's initializer is read in their scope.
460
+ // A local is declared after every static and hides none, but its name is the emitter's to move.
461
+ for (const [name, holder] of addressed) {
462
+ if (bound.has(name)) {
463
+ return {
464
+ symbol: holder,
465
+ refused: `whose initializer holds the address of '${name}', which a parameter or local here also names — it would hide that global`,
466
+ };
467
+ }
468
+ }
336
469
  const callees = new Set<string>([sfn.name]);
337
470
  for (const e of walkExprs(sfn.body)) {
338
471
  if (e.k === 'call' && typeof e.fn === 'string') {
@@ -342,6 +475,9 @@ export function nameLocalStatics(sfn: SFn, objs: LocalObjects): SFn | { symbol:
342
475
  const rename = new Map<string, string>();
343
476
  const bySource = new Map<string, string>();
344
477
  for (const obj of objs.values()) {
478
+ if (obj.anonymous) {
479
+ continue; // no source name: folded into a local's initializer at emission (l3/constinit.ts)
480
+ }
345
481
  const say = (why: string) => ({ symbol: obj.symbol, refused: `whose source name '${obj.name}' ${why}` });
346
482
  const other = bySource.get(obj.name);
347
483
  if (other !== undefined) {
@@ -133,7 +133,7 @@ export interface NameCoalesceDeps {
133
133
  varName: ReadonlyMap<Value, string>;
134
134
  /** name → the type it is declared with */
135
135
  varType: ReadonlyMap<string, IrType>;
136
- /** every natural loop, as its header and the blocks its body contains (structure/loops.ts) */
136
+ /** every natural loop, as its header and the blocks its body contains (ir/loops.ts) */
137
137
  loops: readonly { header: Block; body: ReadonlySet<Block> }[];
138
138
  }
139
139