@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.
- package/README.md +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +165 -50
- package/src/backend/cpp.ts +2 -1
- package/src/backend/pascal.ts +31 -12
- package/src/contracts.ts +173 -16
- package/src/declare.ts +69 -20
- package/src/frontend/asmdata.ts +7 -1
- package/src/frontend/call-plan.ts +964 -60
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +23 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1232 -192
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2076 -215
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2711 -232
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1460 -44
- package/src/frontend/stackargs.ts +106 -57
- package/src/frontend/thumb.ts +2044 -318
- package/src/ir/alias.ts +42 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +100 -5
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +159 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +109 -9
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +80 -11
- package/src/l3/ast.ts +290 -18
- package/src/l3/basecse.ts +33 -5
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +2 -2
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +31 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +9 -4
- package/src/l3/reindex.ts +283 -96
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +4 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +541 -24
- package/src/proto-context.ts +905 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +10 -26
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +102 -19
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +694 -0
- package/src/raise/shortcircuit.ts +26 -12
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +89 -31
- package/src/rank.ts +307 -54
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +165 -2
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +2180 -1054
- package/src/structure/switch-recover.ts +244 -100
- package/src/symbols.ts +6 -0
- package/src/target.ts +765 -52
- package/src/trace.ts +26 -8
- package/src/variation-definitions.ts +126 -14
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +5 -1
package/src/contracts.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { type IrType, memberOf, typeToString } from './ir/types';
|
|
|
11
11
|
import { cellAddress, qualifiedBase } from './l3/address';
|
|
12
12
|
import { type BinOp, type Expr, type SFn, type Stmt, calleeOf } from './l3/ast';
|
|
13
13
|
import {
|
|
14
|
+
assignsIn,
|
|
14
15
|
exprChildren,
|
|
15
16
|
fieldSpellsDot,
|
|
16
17
|
gapReasonFor,
|
|
@@ -19,9 +20,11 @@ import {
|
|
|
19
20
|
stmtChildren,
|
|
20
21
|
stmtExprs,
|
|
21
22
|
stmtLists,
|
|
23
|
+
taintOverAssigns,
|
|
22
24
|
walkExprs,
|
|
23
25
|
} from './l3/ast';
|
|
24
26
|
import { mentionedLocals } from './l3/mentions';
|
|
27
|
+
import { localAddresses } from './l3/storage';
|
|
25
28
|
import { declaredTypes, exprCType } from './l3/typing';
|
|
26
29
|
import { castOffDeclaration } from './l3/volstore';
|
|
27
30
|
|
|
@@ -188,17 +191,23 @@ function qualifiedAccessChain(e: Expr): boolean {
|
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
/** The global an access's address is named by: its base through casts, address arithmetic and the
|
|
191
|
-
* element or member a dot member selects from, down to
|
|
194
|
+
* element or member a dot member selects from, down to `&gSym`, or to a `gSym` that spells its own
|
|
195
|
+
* address — an array's name, which decays to it (`addresses`), or the struct a dot member selects
|
|
196
|
+
* from (`dotted`). Any other bare `gSym` is the object's VALUE: `*(u16 *)gPtr` reads what the
|
|
197
|
+
* pointer points at, not the pointer, so the access names no object. Null too when the base is
|
|
192
198
|
* anything else — another access, a local. */
|
|
193
|
-
function accessedObject(base: Expr): string | null {
|
|
199
|
+
function accessedObject(base: Expr, addresses: ReadonlySet<string>, dotted = false): string | null {
|
|
194
200
|
switch (base.k) {
|
|
195
201
|
case 'var':
|
|
202
|
+
return dotted || addresses.has(base.name) ? base.name : null;
|
|
196
203
|
case 'addr':
|
|
197
204
|
return base.name;
|
|
198
205
|
case 'cast':
|
|
199
|
-
return accessedObject(base.e);
|
|
206
|
+
return accessedObject(base.e, addresses);
|
|
200
207
|
case 'bin':
|
|
201
|
-
return base.op === '+' || base.op === '-'
|
|
208
|
+
return base.op === '+' || base.op === '-'
|
|
209
|
+
? (accessedObject(base.l, addresses) ?? accessedObject(base.r, addresses))
|
|
210
|
+
: null;
|
|
202
211
|
default:
|
|
203
212
|
return null;
|
|
204
213
|
}
|
|
@@ -211,6 +220,9 @@ interface TreeContext {
|
|
|
211
220
|
declared: ReadonlySet<string>;
|
|
212
221
|
written: ReadonlySet<string>;
|
|
213
222
|
typeOf: (e: Expr) => IrType | undefined;
|
|
223
|
+
/** The names whose bare spelling is the object's address: the array-declared globals the
|
|
224
|
+
* structurer subscripts by name (SFn.globals), and the array or struct statics it defines. */
|
|
225
|
+
addresses: ReadonlySet<string>;
|
|
214
226
|
}
|
|
215
227
|
|
|
216
228
|
/** every call under `e`, counted by target name; every qualified memory access, by the address it
|
|
@@ -231,7 +243,9 @@ function effectsInExpr(
|
|
|
231
243
|
): void {
|
|
232
244
|
const bump = (k: string) => into.set(k, (into.get(k) ?? 0) + 1);
|
|
233
245
|
const access = e.k === 'index' || e.k === 'field';
|
|
234
|
-
const
|
|
246
|
+
const inner = access && !selected ? dotSelectedAccess(e) : null;
|
|
247
|
+
const object =
|
|
248
|
+
inner === null ? null : accessedObject(inner.base, ctx.addresses, inner.k === 'field' && fieldSpellsDot(inner));
|
|
235
249
|
const ofDeclared = object !== null && !target && ctx.declared.has(object);
|
|
236
250
|
const writesDeclared = object !== null && target && ctx.written.has(object);
|
|
237
251
|
if (e.k === 'call') {
|
|
@@ -248,7 +262,9 @@ function effectsInExpr(
|
|
|
248
262
|
if (object !== null) {
|
|
249
263
|
bump(`${ofDeclared && castOffDeclaration(e, ctx.typeOf) ? STRIPPED : DECLARED}${object}`);
|
|
250
264
|
}
|
|
251
|
-
} else if (!
|
|
265
|
+
} else if (!target && e.k === 'var' && !(named && (selected || ctx.addresses.has(e.name)))) {
|
|
266
|
+
// A bare name is a read of the object's value unless it spells the address of an access, which
|
|
267
|
+
// counts it: the struct a dot member selects from, or an array's decayed name.
|
|
252
268
|
bump(`${DECLARED}${e.name}`);
|
|
253
269
|
}
|
|
254
270
|
const partOfThis = e.k === 'field' && fieldSpellsDot(e);
|
|
@@ -257,13 +273,15 @@ function effectsInExpr(
|
|
|
257
273
|
);
|
|
258
274
|
}
|
|
259
275
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
276
|
+
type Access = Extract<Expr, { k: 'index' } | { k: 'field' }>;
|
|
277
|
+
|
|
278
|
+
/** A dot member's innermost access, past the element or member it selects from: its base is the
|
|
279
|
+
* address of the one access the member spells. */
|
|
280
|
+
function dotSelectedAccess(e: Access): Access {
|
|
263
281
|
if (e.k === 'field' && fieldSpellsDot(e) && (e.base.k === 'index' || e.base.k === 'field')) {
|
|
264
|
-
return
|
|
282
|
+
return dotSelectedAccess(e.base);
|
|
265
283
|
}
|
|
266
|
-
return e
|
|
284
|
+
return e;
|
|
267
285
|
}
|
|
268
286
|
|
|
269
287
|
/** The executions on the paths through a statement list, split by how each path LEAVES it: falling off
|
|
@@ -499,6 +517,12 @@ export function assertEffectsPreserved(fn: Fn, sfn: SFn): void {
|
|
|
499
517
|
declared: new Set([...irDeclared.keys()].map((k) => k.slice(DECLARED.length))),
|
|
500
518
|
written: declaredWritten,
|
|
501
519
|
typeOf: (e) => exprCType(e, vt),
|
|
520
|
+
addresses: new Set([
|
|
521
|
+
...(sfn.globals ?? []).map((g) => g.name),
|
|
522
|
+
...[...(sfn.statics ?? []), ...(sfn.images ?? [])]
|
|
523
|
+
.filter((st) => st.type.kind === 'array' || st.type.kind === 'struct')
|
|
524
|
+
.map((st) => st.name),
|
|
525
|
+
]),
|
|
502
526
|
};
|
|
503
527
|
const { total, paths } = countEffects(sfn.body, ctx, unreached);
|
|
504
528
|
if (unreached.length) {
|
|
@@ -753,10 +777,17 @@ export function assertLocalsWritten(sfn: SFn): void {
|
|
|
753
777
|
}
|
|
754
778
|
exprChildren(e).forEach(walkExpr);
|
|
755
779
|
};
|
|
780
|
+
// A store to a member of a local struct (`ret.a = v`) writes that local.
|
|
781
|
+
const memberOf = (e: Expr): string | undefined =>
|
|
782
|
+
e.k === 'field' && e.dot === true ? (e.base.k === 'var' ? e.base.name : memberOf(e.base)) : undefined;
|
|
756
783
|
const walkStmt = (st: Stmt): void => {
|
|
757
784
|
if (st.k === 'assign' && suspect.has(st.name)) {
|
|
758
785
|
written.add(st.name);
|
|
759
786
|
}
|
|
787
|
+
const member = st.k === 'store' ? memberOf(st.lval) : undefined;
|
|
788
|
+
if (member !== undefined && suspect.has(member)) {
|
|
789
|
+
written.add(member);
|
|
790
|
+
}
|
|
760
791
|
stmtExprs(st).forEach(walkExpr);
|
|
761
792
|
stmtChildren(st).forEach(walkStmt);
|
|
762
793
|
};
|
|
@@ -858,6 +889,131 @@ export function assertNoOrphanedLocals(before: SFn, after: SFn): void {
|
|
|
858
889
|
}
|
|
859
890
|
}
|
|
860
891
|
|
|
892
|
+
/** The types a tree reads or writes LOCAL STORAGE at, other than an integer — the frame bytes
|
|
893
|
+
* reached by address (l3/storage.ts `localAddresses`), each access typed as it is spelled
|
|
894
|
+
* (`exprCType`: the cast on the storage, or the element struct's field). An aggregate is the
|
|
895
|
+
* element a `.field` selects from, not an access, so only scalars count. */
|
|
896
|
+
function localStorageNonIntegers(sfn: SFn): Set<string> {
|
|
897
|
+
const { isAddress, accessAddress } = localAddresses(sfn);
|
|
898
|
+
const vt = declaredTypes(sfn);
|
|
899
|
+
const out = new Set<string>();
|
|
900
|
+
for (const e of walkExprs(sfn.body)) {
|
|
901
|
+
if ((e.k === 'index' || e.k === 'field') && isAddress(accessAddress(e))) {
|
|
902
|
+
const t = exprCType(e, vt);
|
|
903
|
+
if (t !== undefined && t.kind !== 'int' && t.kind !== 'struct' && t.kind !== 'array') {
|
|
904
|
+
out.add(typeToString(t));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return out;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/** After a respell variation: no access to LOCAL STORAGE is re-typed to a non-integer.
|
|
912
|
+
*
|
|
913
|
+
* The frame audit's licence for a runtime index wider than a byte (frontend/frame-objects.ts
|
|
914
|
+
* `indexedWidth`) rests on what L3 SPELLS: agbcc at -O2 keeps a read ordered after a store to the
|
|
915
|
+
* same bytes only while both are integers of one width, and gives a pointer (or `long`, or a
|
|
916
|
+
* float) an alias set of its own, so a variation that re-types the bytes' reads hands the
|
|
917
|
+
* compiler a stale read the asm never had — C that compiles, scores, and is wrong. `/ptr-field`'s
|
|
918
|
+
* `over-local-storage` gate is the refusal that names itself; this is the backstop for the next
|
|
919
|
+
* variation that re-types, failing at its own stage rather than three away.
|
|
920
|
+
*
|
|
921
|
+
* A DIFFERENTIAL, like `assertNoOrphanedLocals`: it judges only a non-integer type the tree it
|
|
922
|
+
* derives from did not already read the storage at. The default tree is the frame audit's
|
|
923
|
+
* answer, and a type it gives a local object (a header-typed out-parameter's pointer member, say)
|
|
924
|
+
* is the source's own spelling, not a variation's claim. */
|
|
925
|
+
export function assertLocalStorageStaysInteger(before: SFn, after: SFn): void {
|
|
926
|
+
const had = localStorageNonIntegers(before);
|
|
927
|
+
const added = [...localStorageNonIntegers(after)].filter((t) => !had.has(t));
|
|
928
|
+
if (added.length) {
|
|
929
|
+
throw new ContractError(
|
|
930
|
+
`a respell variation re-typed an access to local storage in '${after.name}' as ${added
|
|
931
|
+
.sort()
|
|
932
|
+
.map((t) => `'${t}'`)
|
|
933
|
+
.join(', ')} — not an integer, so agbcc may read it stale past an integer store to the same bytes`,
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/** A FRAME UNION IS READ AND WRITTEN ONLY THROUGH ITS VIEWS. The frame audit declares storage
|
|
939
|
+
* accessed at two widths over one byte as a union of per-width views (frontend/frame-objects.ts
|
|
940
|
+
* `unionViews`, structure.ts `laddrType`), because agbcc gives an access through a union member
|
|
941
|
+
* alias set 0 and reorders nothing past it — where a cast on the same bytes is read stale past a
|
|
942
|
+
* store of the other width. That licence holds only while EVERY access is a member access:
|
|
943
|
+
* - the union's name appears only as the base of a view (`u.half`), and an element of an array
|
|
944
|
+
* view is subscripted, element 0 included — `*u.half` dereferences the view's decay, an
|
|
945
|
+
* INDIRECT_REF agbcc types by its pointee and reads stale exactly as the cast (compiled);
|
|
946
|
+
* - nothing is accessed through its ADDRESS: `&u`, under casts and `+`/`-`, or a local ever
|
|
947
|
+
* assigned such a value, as the base of a subscript, an arrow or a deref is the cast
|
|
948
|
+
* spelling again. The address itself may go anywhere a value goes — a call's argument, a
|
|
949
|
+
* device's source register — since handing it on accesses nothing.
|
|
950
|
+
* ABSOLUTE, not a differential: the default tree satisfies it by construction, so any tree that
|
|
951
|
+
* does not was made by a variation. Checked on the default tree (pipeline.ts) and on every
|
|
952
|
+
* respelled one (rank.ts, beside `assertLocalStorageStaysInteger`). */
|
|
953
|
+
export function assertFrameUnionsViewed(sfn: SFn): void {
|
|
954
|
+
const unions = new Map(sfn.locals.filter((l) => l.type.kind === 'union').map((l) => [l.name, l.type]));
|
|
955
|
+
if (unions.size === 0) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const bad = (why: string): never => {
|
|
959
|
+
throw new ContractError(`'${sfn.name}' ${why} — agbcc may read the frame union stale through it`);
|
|
960
|
+
};
|
|
961
|
+
const reaches = (e: Expr, held: ReadonlySet<string>): boolean => {
|
|
962
|
+
switch (e.k) {
|
|
963
|
+
case 'addr':
|
|
964
|
+
return unions.has(e.name);
|
|
965
|
+
case 'var':
|
|
966
|
+
return held.has(e.name);
|
|
967
|
+
case 'cast':
|
|
968
|
+
return reaches(e.e, held);
|
|
969
|
+
case 'bin':
|
|
970
|
+
return (e.op === '+' || e.op === '-') && (reaches(e.l, held) || reaches(e.r, held));
|
|
971
|
+
default:
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
const held = taintOverAssigns(assignsIn(sfn.body), [], (a, t) => reaches(a.value, t));
|
|
976
|
+
const isView = (e: Expr): boolean =>
|
|
977
|
+
e.k === 'field' && e.dot === true && e.base.k === 'var' && unions.has(e.base.name);
|
|
978
|
+
const visit = (e: Expr): void => {
|
|
979
|
+
if (e.k === 'field' && isView(e)) {
|
|
980
|
+
if (memberOf(unions.get((e.base as { name: string }).name), e.name) === undefined) {
|
|
981
|
+
bad(`names '${e.name}', which is no view of the frame union`);
|
|
982
|
+
}
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (e.k === 'index' && isView(e.base)) {
|
|
986
|
+
if (e.idx.k === 'const' && e.idx.value === 0 && e.subscript !== true) {
|
|
987
|
+
bad('dereferences a view of a frame union (`*u.view`) instead of subscripting it');
|
|
988
|
+
}
|
|
989
|
+
visit(e.base);
|
|
990
|
+
visit(e.idx);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if ((e.k === 'index' || (e.k === 'field' && e.dot !== true)) && reaches(e.base, held)) {
|
|
994
|
+
bad('accesses a frame union through a pointer to its bytes');
|
|
995
|
+
}
|
|
996
|
+
if (e.k === 'var' && unions.has(e.name)) {
|
|
997
|
+
bad(`uses the frame union '${e.name}' other than through a view`);
|
|
998
|
+
}
|
|
999
|
+
exprChildren(e).forEach(visit);
|
|
1000
|
+
};
|
|
1001
|
+
const walk = (list: readonly Stmt[]): void => {
|
|
1002
|
+
for (const st of list) {
|
|
1003
|
+
if (st.k === 'assign' && unions.has(st.name)) {
|
|
1004
|
+
bad(`assigns the frame union '${st.name}' whole`);
|
|
1005
|
+
}
|
|
1006
|
+
stmtExprs(st).forEach(visit);
|
|
1007
|
+
// a `for`'s init and inc are statements of their own, outside both lists
|
|
1008
|
+
stmtLists(st).forEach(walk);
|
|
1009
|
+
if (st.k === 'for') {
|
|
1010
|
+
walk([st.init, st.inc]);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
walk(sfn.body);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
861
1017
|
/** After a respell variation: every read of a MINTED local — its ADDRESS being taken included — must sit where
|
|
862
1018
|
* that local's assignment has already run.
|
|
863
1019
|
*
|
|
@@ -1013,8 +1169,9 @@ export function assertDerefsTyped(sfn: SFn): void {
|
|
|
1013
1169
|
if (e.k === 'index' && !structElem.has(e) && !SCALAR_WIDTHS.has(e.width)) {
|
|
1014
1170
|
bad.push(`index width ${e.width} is not a C scalar width`);
|
|
1015
1171
|
}
|
|
1016
|
-
// The emitter legalizes pointer operands away from these ops (structure.ts
|
|
1017
|
-
// pointer surviving here is ill-typed C the compiler will
|
|
1172
|
+
// The emitter legalizes pointer operands away from these ops (structure/pointer-spelling.ts
|
|
1173
|
+
// `ARITH_ROWS`, its `bitwise` row); a pointer surviving here is ill-typed C the compiler will
|
|
1174
|
+
// reject.
|
|
1018
1175
|
if (e.k === 'bin' && NO_PTR_OPS.has(e.op)) {
|
|
1019
1176
|
for (const side of [e.l, e.r]) {
|
|
1020
1177
|
if (ctype(side)?.kind === 'ptr') {
|
|
@@ -1026,9 +1183,9 @@ export function assertDerefsTyped(sfn: SFn): void {
|
|
|
1026
1183
|
// offset by sizeof(SYM), which is unknown for a header-typed global, so `&SYM + N` is byte-
|
|
1027
1184
|
// inexact. Nothing emits this shape anymore: a load/store base folds byte-correctly (globalOf
|
|
1028
1185
|
// turns `&SYM + N` into an `index`/`field` node whose base is a bare `addr`), and the additive
|
|
1029
|
-
// lowering intifies every other `addr` operand to `(u32)&SYM` (structure.ts
|
|
1030
|
-
// cast types int, so it never lands here). A bare `addr` reaching a `+`/`-`
|
|
1031
|
-
// a lowering REGRESSION — flag it rather than emit wrong bytes.
|
|
1186
|
+
// lowering intifies every other `addr` operand to `(u32)&SYM` (structure/pointer-spelling.ts
|
|
1187
|
+
// ARITH_ROWS — the cast types int, so it never lands here). A bare `addr` reaching a `+`/`-`
|
|
1188
|
+
// operand is therefore a lowering REGRESSION — flag it rather than emit wrong bytes.
|
|
1032
1189
|
if (e.k === 'bin' && (e.op === '+' || e.op === '-')) {
|
|
1033
1190
|
const addrSide = e.l.k === 'addr' ? e.l : e.r.k === 'addr' ? e.r : undefined;
|
|
1034
1191
|
if (addrSide) {
|
package/src/declare.ts
CHANGED
|
@@ -185,7 +185,36 @@ export function renderDeclarations(refs: SymbolRef[]): string {
|
|
|
185
185
|
const lines: string[] = [];
|
|
186
186
|
const declaredTags = new Set<string>();
|
|
187
187
|
const declaredTypedefs = new Set<string>();
|
|
188
|
-
|
|
188
|
+
const defineStruct = (s: { readonly name: string; readonly declared: string; readonly layout: AggregateLayout }) => {
|
|
189
|
+
if (!declaredTags.has(s.name)) {
|
|
190
|
+
declaredTags.add(s.name);
|
|
191
|
+
lines.push(declaredStructDecl(s.name, s.layout));
|
|
192
|
+
}
|
|
193
|
+
if (!/^(?:struct|union)\s/.test(s.declared) && !declaredTypedefs.has(s.declared)) {
|
|
194
|
+
declaredTypedefs.add(s.declared);
|
|
195
|
+
lines.push(`typedef struct ${s.name} ${s.declared};`);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
for (const {
|
|
199
|
+
name,
|
|
200
|
+
info,
|
|
201
|
+
access,
|
|
202
|
+
proto,
|
|
203
|
+
returned,
|
|
204
|
+
passed,
|
|
205
|
+
passedAs,
|
|
206
|
+
holdsPointer,
|
|
207
|
+
implicitCall,
|
|
208
|
+
definesReturn,
|
|
209
|
+
} of refs) {
|
|
210
|
+
// THE FUNCTION'S OWN STRUCT RETURN, defined as a callee's is; the function itself is declared
|
|
211
|
+
// by its definition
|
|
212
|
+
if (definesReturn) {
|
|
213
|
+
if (returned !== undefined) {
|
|
214
|
+
defineStruct(returned);
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
189
218
|
// An address-cast macro declares itself: the header's own body, verbatim. It must NOT become
|
|
190
219
|
// an `extern` — that is the whole point of the fact (an extern emits a relocated pool word
|
|
191
220
|
// where the macro emits the numeric one the target shows).
|
|
@@ -193,6 +222,14 @@ export function renderDeclarations(refs: SymbolRef[]): string {
|
|
|
193
222
|
lines.push(`#define ${name} ${info.macroBody}`);
|
|
194
223
|
continue;
|
|
195
224
|
}
|
|
225
|
+
// A GLOBAL REGISTER VARIABLE declares itself in its register (SymbolInfo.globalRegister), typed
|
|
226
|
+
// as a name-only global is: a pointer where the body stores or meets one, else the word the
|
|
227
|
+
// body accesses.
|
|
228
|
+
if (info.globalRegister !== undefined) {
|
|
229
|
+
const t = holdsPointer ? 'void *' : `${intType(access?.width ?? 4, access?.signed ?? true) ?? 's32'} `;
|
|
230
|
+
lines.push(`register ${t}${name} __asm__("${info.globalRegister}");`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
196
233
|
// A CALL TARGET the project declared completely — its own prototype, re-spelled from the
|
|
197
234
|
// parameter and return type texts the project wrote. This is the one declaration here that
|
|
198
235
|
// is load-bearing for the CALL and not only for the name: an implicit declaration is `int`,
|
|
@@ -203,23 +240,24 @@ export function renderDeclarations(refs: SymbolRef[]): string {
|
|
|
203
240
|
// A STRUCT IT RETURNS THROUGH MEMORY is the headers' type, spelled as they spell it, so it is
|
|
204
241
|
// defined here with the rest of what they own: by its tag, once, whoever else declares that
|
|
205
242
|
// tag — and a typedef name is bound to it on top.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
lines.push(declaredStructDecl(returned.name, returned.layout));
|
|
210
|
-
}
|
|
211
|
-
if (!/^(?:struct|union)\s/.test(returned.declared) && !declaredTypedefs.has(returned.declared)) {
|
|
212
|
-
declaredTypedefs.add(returned.declared);
|
|
213
|
-
lines.push(`typedef struct ${returned.name} ${returned.declared};`);
|
|
214
|
-
}
|
|
243
|
+
// …and so is a struct it is passed by value, the same way.
|
|
244
|
+
for (const s of [...(returned?.declared !== undefined ? [returned] : []), ...(passed ?? [])]) {
|
|
245
|
+
defineStruct(s);
|
|
215
246
|
}
|
|
216
247
|
lines.push(`${proto.returns} ${name}(${proto.params.length > 0 ? proto.params.join(', ') : 'void'});`);
|
|
217
248
|
continue;
|
|
218
249
|
}
|
|
250
|
+
// A CALL TARGET no prototype spells, named ahead of its first call by an initializer — C's
|
|
251
|
+
// implicit declaration, `int F();`, written where the initializer can see it. No parameter
|
|
252
|
+
// list, so the call binds to it exactly as it bound to the implicit one (see collectSymbolRefs).
|
|
253
|
+
if (implicitCall) {
|
|
254
|
+
lines.push(`int ${name}();`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
219
257
|
if (info.kind === 'code') {
|
|
220
258
|
// value-referenced code symbol ((u32)Func): any prototype makes the name visible, and
|
|
221
|
-
// the address is arity-independent. A call target reaches
|
|
222
|
-
// prototype, which the
|
|
259
|
+
// the address is arity-independent. A call target reaches here only with a complete
|
|
260
|
+
// prototype or as an initializer's implicit call, which the branches above printed.
|
|
223
261
|
lines.push(`void ${name}(void);`);
|
|
224
262
|
continue;
|
|
225
263
|
}
|
|
@@ -317,6 +355,12 @@ export function renderDeclarations(refs: SymbolRef[]): string {
|
|
|
317
355
|
lines.push(`extern void *${info.volatile ? 'volatile ' : ''}${info.const ? 'const ' : ''}${name};`);
|
|
318
356
|
break;
|
|
319
357
|
}
|
|
358
|
+
// A global passed bare as a struct by value is that struct, which its callee's prototype
|
|
359
|
+
// defines (`passed`); a declaration of an incomplete tag is completed by that definition.
|
|
360
|
+
if (passedAs !== undefined) {
|
|
361
|
+
lines.push(`extern ${quals(info)}${passedAs} ${name};`);
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
320
364
|
const t = access ? intType(access.width, access.signed) : null;
|
|
321
365
|
lines.push(`extern ${quals(info)}${t ?? 'u32'} ${name};`);
|
|
322
366
|
break;
|
|
@@ -326,18 +370,23 @@ export function renderDeclarations(refs: SymbolRef[]): string {
|
|
|
326
370
|
return lines.length ? lines.join('\n') + '\n' : '';
|
|
327
371
|
}
|
|
328
372
|
|
|
329
|
-
/**
|
|
373
|
+
/** THE LINES OF A RENDERED DECLARATION BLOCK THAT THE HEADERS WORLD KEEPS: its object-like
|
|
374
|
+
* `#define`s and its global register variables. Everything else there is owned by the injected
|
|
375
|
+
* headers (a duplicate typedef or struct definition is a C89 hard error), and these two are not:
|
|
376
|
+
*
|
|
377
|
+
* - an address-cast macro, because a duplicate `#define` with an identical body is legal — and a
|
|
378
|
+
* PREPROCESSED project context has no macros left at all, so dropping these turns a macro-named
|
|
379
|
+
* candidate into an `undeclared identifier` rather than a spelling choice;
|
|
380
|
+
* - a global register variable (`register T greg_r8 __asm__("r8");`, SymbolInfo.globalRegister),
|
|
381
|
+
* because no header declares the name the lift made up for it.
|
|
330
382
|
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
|
|
334
|
-
* legal — and a PREPROCESSED project context has no macros left at all, so dropping these turns a
|
|
335
|
-
* macro-named candidate into an `undeclared identifier` rather than a spelling choice. */
|
|
336
|
-
export function macroDefinesOf(declarations: string | undefined): string {
|
|
383
|
+
* Chosen by each line's leading keyword, which `renderDeclarations` prints for these two kinds
|
|
384
|
+
* only: a new kind of line it prints with either keyword rides into the headers world too. */
|
|
385
|
+
export function declarationsKeptWithHeaders(declarations: string | undefined): string {
|
|
337
386
|
if (!declarations) {
|
|
338
387
|
return '';
|
|
339
388
|
}
|
|
340
|
-
const lines = declarations.split('\n').filter((l) => l.startsWith('#define '));
|
|
389
|
+
const lines = declarations.split('\n').filter((l) => l.startsWith('#define ') || l.startsWith('register '));
|
|
341
390
|
return lines.length ? lines.join('\n') + '\n' : '';
|
|
342
391
|
}
|
|
343
392
|
|
package/src/frontend/asmdata.ts
CHANGED
|
@@ -50,8 +50,14 @@ const readU32 = (b: Uint8Array, off: number, big: boolean): number =>
|
|
|
50
50
|
? ((b[off] << 24) | (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3]) >>> 0
|
|
51
51
|
: ((b[off + 3] << 24) | (b[off + 2] << 16) | (b[off + 1] << 8) | b[off]) >>> 0;
|
|
52
52
|
|
|
53
|
-
// Parse an `objdump -r` VALUE field: `sw_jt+0x00000020` / `.text` / `@15` → {sym, addend}.
|
|
53
|
+
// Parse an `objdump -r` VALUE field: `sw_jt+0x00000020` / `.text` / `@15` → {sym, addend}. A
|
|
54
|
+
// negative RELA addend prints as `garr-0x00000004` (mwcc's `&garr[-1]`, compiled), and is kept as
|
|
55
|
+
// the same 32-bit word a positive one is.
|
|
54
56
|
function parseRelocValue(v: string): { sym: string; addend: number } {
|
|
57
|
+
const minus = v.match(/^(.+)-(0x[0-9a-f]+)$/i);
|
|
58
|
+
if (minus) {
|
|
59
|
+
return { sym: minus[1], addend: -parseInt(minus[2], 16) >>> 0 };
|
|
60
|
+
}
|
|
55
61
|
const plus = v.indexOf('+');
|
|
56
62
|
if (plus < 0) {
|
|
57
63
|
return { sym: v, addend: 0 };
|