@asmlift/core 0.1.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -0
  3. package/package.json +14 -0
  4. package/src/backend/c.ts +20 -0
  5. package/src/backend/cfamily.ts +352 -0
  6. package/src/backend/cpp.ts +145 -0
  7. package/src/backend/pascal.ts +279 -0
  8. package/src/contracts.ts +131 -0
  9. package/src/detect.ts +12 -0
  10. package/src/frontend/asmdata.ts +170 -0
  11. package/src/frontend/disasm.ts +102 -0
  12. package/src/frontend/emit.ts +57 -0
  13. package/src/frontend/errors.ts +14 -0
  14. package/src/frontend/format.ts +47 -0
  15. package/src/frontend/frontend.ts +22 -0
  16. package/src/frontend/mips.ts +875 -0
  17. package/src/frontend/opaque.ts +82 -0
  18. package/src/frontend/ppc.ts +990 -0
  19. package/src/frontend/registry.ts +34 -0
  20. package/src/frontend/ssa.ts +214 -0
  21. package/src/frontend/thumb.ts +1419 -0
  22. package/src/ir/core.ts +104 -0
  23. package/src/ir/opcodes.ts +143 -0
  24. package/src/ir/parse.ts +221 -0
  25. package/src/ir/print.ts +77 -0
  26. package/src/ir/types.ts +106 -0
  27. package/src/ir/verify.ts +221 -0
  28. package/src/l3/ast.ts +301 -0
  29. package/src/l3/basecse.ts +218 -0
  30. package/src/l3/dce.ts +256 -0
  31. package/src/l3/regspell.ts +331 -0
  32. package/src/l3/reindex.ts +447 -0
  33. package/src/l3/typing.ts +145 -0
  34. package/src/mangle.ts +135 -0
  35. package/src/pattern/engine.ts +392 -0
  36. package/src/pipeline.ts +272 -0
  37. package/src/proto.ts +42 -0
  38. package/src/raise/arrays.ts +84 -0
  39. package/src/raise/const.ts +52 -0
  40. package/src/raise/errors.ts +10 -0
  41. package/src/raise/magicdiv.ts +386 -0
  42. package/src/raise/pre-recovery.ts +71 -0
  43. package/src/raise/recover.ts +215 -0
  44. package/src/raise/retsink.ts +72 -0
  45. package/src/raise/shortcircuit.ts +207 -0
  46. package/src/raise/softdiv.ts +62 -0
  47. package/src/raise/struct-arrays.ts +257 -0
  48. package/src/raise/structs.ts +223 -0
  49. package/src/rank.ts +208 -0
  50. package/src/structure/analysis.ts +410 -0
  51. package/src/structure/hazards.ts +142 -0
  52. package/src/structure/loops.ts +169 -0
  53. package/src/structure/structure.ts +1726 -0
  54. package/src/structure/switch-recover.ts +410 -0
  55. package/src/target.ts +140 -0
  56. package/src/trace.ts +233 -0
@@ -0,0 +1,82 @@
1
+ // asmlift — the single place that decides how an unmodelled instruction degrades its register
2
+ // destination. Every ISA frontend routes its decode `default` (and any case it chooses not to
3
+ // model) through `opaqueDest`, so the contract — "an unmodelled instruction/operand degrades to a
4
+ // LOUD failure, never a silent drop" — has ONE implementation. Every registered frontend is held
5
+ // to it in test/contract-invariant.test.ts.
6
+ //
7
+ // This module owns only the POLICY (which token is the destination, which are register sources).
8
+ // The frontend still owns the SSA plumbing (how to `read` a source and `write`/emit the result),
9
+ // because that is block-local state the policy must not touch.
10
+ import { FrontendUnsupportedError } from './errors';
11
+
12
+ export interface OpaquePolicy {
13
+ /** true iff the token is a WRITABLE register destination in this ISA (post-`normalize`). */
14
+ isReg: (s: string | undefined) => s is string;
15
+ /** token cleanup before classification — e.g. Thumb strips `[`/`]` off a memory operand.
16
+ * Default: identity. */
17
+ normalize?: (s: string) => string;
18
+ /** true iff the register is hardwired-zero (MIPS `$zero`/`$0`): writing it is a no-op, so it is
19
+ * NOT a real destination and the instruction is safe to skip. Default: nothing is zero. */
20
+ isZero?: (r: string) => boolean;
21
+ /** Mnemonics that WRITE MEMORY in this ISA: the "no register destination ⇒ safe to fall
22
+ * through" premise is FALSE for stores — skipping one silently deletes the write (Thumb
23
+ * `stmia r0!, {…}`), and a store whose FIRST token is a register (MIPS `swl rt, off(base)`)
24
+ * would otherwise fabricate an opaque write to what is actually a SOURCE. An unmodelled
25
+ * instruction matching this pattern throws instead. Default: none (a frontend that models
26
+ * every store may omit it, but registered frontends are held to supplying one). */
27
+ storeClass?: RegExp;
28
+ /** attribution for thrown declines: the function being lifted (optionally "+ site"). */
29
+ context?: string;
30
+ /** Mnemonics PROVABLY effect-free — or deliberately transparent (Thumb push/pop frame ops) —
31
+ * in this ISA: the ONLY unmodelled no-destination instructions that may be skipped. Any other
32
+ * no-destination unmodelled instruction THROWS: a side-effect-only instruction (swi, syscall,
33
+ * sync, cache…) skipped silently is a deleted effect — a silent miscompile. Default: none. */
34
+ skipSafe?: RegExp;
35
+ }
36
+
37
+ export interface OpaqueDest {
38
+ /** the destination register token to make `opaque` (guaranteed a writable, non-zero reg). */
39
+ dst: string;
40
+ /** the register source-operand tokens, in order, to feed the `opaque` op (already normalized). */
41
+ srcRegs: string[];
42
+ }
43
+
44
+ /** Decide the opaque destination + register source operands for an unmodelled instruction
45
+ * `mnemonic` with operand list `ops` (operands in objdump order — destination first). Returns
46
+ * `null` only when the instruction is provably skippable: a write to a hardwired-zero register,
47
+ * or a policy.skipSafe mnemonic. An unmodelled STORE-CLASS instruction (policy.storeClass)
48
+ * throws loud (a skipped memory write is a silent miscompile) — and so does any OTHER
49
+ * no-destination instruction not in skipSafe: with no register to degrade to a live-`?`
50
+ * sentinel, skipping would silently delete a side effect (swi/syscall/sync/cache).
51
+ *
52
+ * When non-null, the caller MUST emit an `opaque` op that writes `dst` and consumes `srcRegs`
53
+ * (read through the frontend's own SSA): a DEAD opaque is DCE'd away harmlessly, while a LIVE one
54
+ * reaches structuring as the sentinel `?` and trips `assertResolved` — the loud failure the
55
+ * contract requires, instead of a stale/absent value surfacing as confidently-wrong source. */
56
+ export function opaqueDest(mnemonic: string, ops: string[], policy: OpaquePolicy): OpaqueDest | null {
57
+ if (policy.storeClass?.test(mnemonic)) {
58
+ // `context` names the function (and, where the ISA has addresses, the site) — this message
59
+ // lands verbatim in annotate-mode stub headers, where an un-attributed decline is
60
+ // unactionable in a multi-function run.
61
+ const where = policy.context ? `cannot lift '${policy.context}': ` : '';
62
+ throw new FrontendUnsupportedError(
63
+ `${where}unmodelled store-class instruction '${mnemonic}' — a memory write cannot be skipped or degraded to a register opaque`,
64
+ );
65
+ }
66
+ const norm = policy.normalize ?? ((s) => s);
67
+ const dst = norm(ops[0] ?? '');
68
+ if (!policy.isReg(dst)) {
69
+ if (policy.skipSafe?.test(mnemonic)) {
70
+ return null;
71
+ } // explicitly transparent for this ISA
72
+ const where = policy.context ? `cannot lift '${policy.context}': ` : '';
73
+ throw new FrontendUnsupportedError(
74
+ `${where}unmodelled effect instruction '${mnemonic}' — no register destination to degrade, and skipping it would silently delete its effect`,
75
+ );
76
+ }
77
+ if (policy.isZero?.(dst)) {
78
+ return null;
79
+ } // writes hardwired zero → a genuine no-op
80
+ const srcRegs = ops.slice(1).map(norm).filter(policy.isReg);
81
+ return { dst, srcRegs };
82
+ }