@jhlagado/azm 0.2.7 → 0.2.8

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 (235) hide show
  1. package/README.md +170 -69
  2. package/dist/src/api-artifacts.d.ts +20 -0
  3. package/dist/src/api-artifacts.js +165 -0
  4. package/dist/src/api-compile.d.ts +8 -2
  5. package/dist/src/api-compile.js +31 -230
  6. package/dist/src/api-register-contracts.d.ts +9 -0
  7. package/dist/src/api-register-contracts.js +77 -0
  8. package/dist/src/api-tooling.d.ts +2 -2
  9. package/dist/src/api-tooling.js +1 -1
  10. package/dist/src/assembly/address-planning.d.ts +1 -2
  11. package/dist/src/assembly/address-planning.js +119 -218
  12. package/dist/src/assembly/address-symbols.d.ts +12 -0
  13. package/dist/src/assembly/address-symbols.js +118 -0
  14. package/dist/src/assembly/fixup-emission.js +30 -48
  15. package/dist/src/assembly/program-emission.js +163 -164
  16. package/dist/src/cli/artifact-files.d.ts +15 -0
  17. package/dist/src/cli/artifact-files.js +86 -0
  18. package/dist/src/cli/parse-args.d.ts +6 -5
  19. package/dist/src/cli/parse-args.js +162 -136
  20. package/dist/src/cli/run.js +4 -1
  21. package/dist/src/cli/usage.d.ts +1 -0
  22. package/dist/src/cli/usage.js +33 -0
  23. package/dist/src/cli/write-artifacts.js +18 -91
  24. package/dist/src/core/compile.js +51 -274
  25. package/dist/src/core/conditional-assembly.d.ts +6 -0
  26. package/dist/src/core/conditional-assembly.js +181 -0
  27. package/dist/src/expansion/op-constant-expression.d.ts +3 -0
  28. package/dist/src/expansion/op-constant-expression.js +52 -0
  29. package/dist/src/expansion/op-expand-selected.d.ts +5 -0
  30. package/dist/src/expansion/op-expand-selected.js +143 -0
  31. package/dist/src/expansion/op-expansion.d.ts +5 -53
  32. package/dist/src/expansion/op-expansion.js +85 -815
  33. package/dist/src/expansion/op-instruction-instantiation.d.ts +3 -0
  34. package/dist/src/expansion/op-instruction-instantiation.js +194 -0
  35. package/dist/src/expansion/op-local-labels.d.ts +8 -0
  36. package/dist/src/expansion/op-local-labels.js +166 -0
  37. package/dist/src/expansion/op-operand-splitting.d.ts +1 -0
  38. package/dist/src/expansion/op-operand-splitting.js +44 -0
  39. package/dist/src/expansion/op-operands.d.ts +53 -0
  40. package/dist/src/expansion/op-operands.js +66 -0
  41. package/dist/src/expansion/op-selection.d.ts +18 -0
  42. package/dist/src/expansion/op-selection.js +172 -0
  43. package/dist/src/index.d.ts +2 -1
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/model/diagnostic.d.ts +4 -0
  46. package/dist/src/model/diagnostic.js +4 -0
  47. package/dist/src/outputs/asm80-expression-evaluation.d.ts +10 -0
  48. package/dist/src/outputs/asm80-expression-evaluation.js +75 -0
  49. package/dist/src/outputs/asm80-expressions.d.ts +5 -0
  50. package/dist/src/outputs/asm80-expressions.js +47 -0
  51. package/dist/src/outputs/asm80-instruction-operands.d.ts +16 -0
  52. package/dist/src/outputs/asm80-instruction-operands.js +38 -0
  53. package/dist/src/outputs/asm80-instructions.d.ts +5 -0
  54. package/dist/src/outputs/asm80-instructions.js +272 -0
  55. package/dist/src/outputs/asm80-ld-operands.d.ts +10 -0
  56. package/dist/src/outputs/asm80-ld-operands.js +157 -0
  57. package/dist/src/outputs/asm80-strings.d.ts +4 -0
  58. package/dist/src/outputs/asm80-strings.js +14 -0
  59. package/dist/src/outputs/d8-files.d.ts +10 -0
  60. package/dist/src/outputs/d8-files.js +103 -0
  61. package/dist/src/outputs/d8-helpers.d.ts +21 -0
  62. package/dist/src/outputs/d8-helpers.js +136 -0
  63. package/dist/src/outputs/hex.js +26 -18
  64. package/dist/src/outputs/types.d.ts +16 -10
  65. package/dist/src/outputs/write-asm80.js +68 -597
  66. package/dist/src/outputs/write-d8.js +6 -216
  67. package/dist/src/register-contracts/accept-output.d.ts +2 -0
  68. package/dist/src/register-contracts/analyze-helpers.d.ts +29 -0
  69. package/dist/src/register-contracts/analyze-helpers.js +162 -0
  70. package/dist/src/{register-care → register-contracts}/analyze.d.ts +6 -6
  71. package/dist/src/register-contracts/analyze.js +73 -0
  72. package/dist/src/register-contracts/annotate.d.ts +11 -0
  73. package/dist/src/{register-care → register-contracts}/annotate.js +3 -3
  74. package/dist/src/register-contracts/annotations.d.ts +8 -0
  75. package/dist/src/{register-care → register-contracts}/annotations.js +3 -3
  76. package/dist/src/register-contracts/boundaryHints.d.ts +3 -0
  77. package/dist/src/register-contracts/boundaryHints.js +24 -0
  78. package/dist/src/register-contracts/carriers.d.ts +2 -0
  79. package/dist/src/register-contracts/constants.d.ts +4 -0
  80. package/dist/src/register-contracts/constants.js +51 -0
  81. package/dist/src/register-contracts/controlFlow.d.ts +5 -0
  82. package/dist/src/register-contracts/controlFlow.js +55 -0
  83. package/dist/src/register-contracts/fix.d.ts +11 -0
  84. package/dist/src/{register-care → register-contracts}/fix.js +47 -30
  85. package/dist/src/register-contracts/instruction-head.d.ts +2 -0
  86. package/dist/src/register-contracts/instruction-head.js +3 -0
  87. package/dist/src/register-contracts/instruction-operands.d.ts +3 -0
  88. package/dist/src/register-contracts/instruction-operands.js +101 -0
  89. package/dist/src/register-contracts/instruction-predicates.d.ts +6 -0
  90. package/dist/src/register-contracts/instruction-predicates.js +44 -0
  91. package/dist/src/register-contracts/interfaceContracts.d.ts +2 -0
  92. package/dist/src/register-contracts/interfaceContracts.js +68 -0
  93. package/dist/src/register-contracts/liveness.d.ts +3 -0
  94. package/dist/src/{register-care → register-contracts}/liveness.js +111 -79
  95. package/dist/src/register-contracts/operand-register-name.d.ts +2 -0
  96. package/dist/src/register-contracts/operand-register-name.js +13 -0
  97. package/dist/src/{register-care → register-contracts}/profiles.d.ts +5 -5
  98. package/dist/src/{register-care → register-contracts}/profiles.js +2 -2
  99. package/dist/src/register-contracts/programModel-boundaries.d.ts +6 -0
  100. package/dist/src/register-contracts/programModel-boundaries.js +64 -0
  101. package/dist/src/register-contracts/programModel-routines.d.ts +7 -0
  102. package/dist/src/register-contracts/programModel-routines.js +128 -0
  103. package/dist/src/register-contracts/programModel.d.ts +3 -0
  104. package/dist/src/register-contracts/programModel.js +14 -0
  105. package/dist/src/register-contracts/report.d.ts +5 -0
  106. package/dist/src/{register-care → register-contracts}/report.js +34 -17
  107. package/dist/src/register-contracts/routine-summaries.d.ts +6 -0
  108. package/dist/src/{register-care → register-contracts}/routine-summaries.js +11 -1
  109. package/dist/src/register-contracts/smartCommentBlocks.d.ts +5 -0
  110. package/dist/src/register-contracts/smartCommentBlocks.js +30 -0
  111. package/dist/src/register-contracts/smartCommentParsing.d.ts +3 -0
  112. package/dist/src/register-contracts/smartCommentParsing.js +80 -0
  113. package/dist/src/register-contracts/smartComments.d.ts +5 -0
  114. package/dist/src/register-contracts/smartComments.js +92 -0
  115. package/dist/src/register-contracts/summaries.d.ts +12 -0
  116. package/dist/src/{register-care → register-contracts}/summaries.js +7 -7
  117. package/dist/src/register-contracts/summary-boundary.d.ts +2 -0
  118. package/dist/src/register-contracts/summary-boundary.js +40 -0
  119. package/dist/src/register-contracts/summary-contract.d.ts +2 -0
  120. package/dist/src/register-contracts/summary-contract.js +45 -0
  121. package/dist/src/register-contracts/summary-result.d.ts +7 -0
  122. package/dist/src/register-contracts/summary-result.js +122 -0
  123. package/dist/src/register-contracts/summary-state.d.ts +23 -0
  124. package/dist/src/register-contracts/summary-state.js +88 -0
  125. package/dist/src/register-contracts/summary-token-transfer.d.ts +3 -0
  126. package/dist/src/register-contracts/summary-token-transfer.js +67 -0
  127. package/dist/src/register-contracts/summary.d.ts +3 -0
  128. package/dist/src/register-contracts/summary.js +266 -0
  129. package/dist/src/register-contracts/tooling.d.ts +57 -0
  130. package/dist/src/{register-care → register-contracts}/tooling.js +8 -6
  131. package/dist/src/register-contracts/types.d.ts +188 -0
  132. package/dist/src/semantics/binary-operators.d.ts +2 -0
  133. package/dist/src/semantics/binary-operators.js +15 -0
  134. package/dist/src/semantics/byte-functions.d.ts +2 -0
  135. package/dist/src/semantics/byte-functions.js +7 -0
  136. package/dist/src/semantics/constant-operator-types.d.ts +10 -0
  137. package/dist/src/semantics/constant-operator-types.js +1 -0
  138. package/dist/src/semantics/constant-operators.d.ts +3 -0
  139. package/dist/src/semantics/constant-operators.js +3 -0
  140. package/dist/src/semantics/diagnostics.d.ts +3 -0
  141. package/dist/src/semantics/diagnostics.js +10 -0
  142. package/dist/src/semantics/expression-evaluation.d.ts +11 -19
  143. package/dist/src/semantics/expression-evaluation.js +22 -334
  144. package/dist/src/semantics/layout-evaluation.d.ts +23 -0
  145. package/dist/src/semantics/layout-evaluation.js +202 -0
  146. package/dist/src/semantics/layout-format.d.ts +5 -0
  147. package/dist/src/semantics/layout-format.js +31 -0
  148. package/dist/src/semantics/layout-path.d.ts +24 -0
  149. package/dist/src/semantics/layout-path.js +58 -0
  150. package/dist/src/semantics/unary-operators.d.ts +2 -0
  151. package/dist/src/semantics/unary-operators.js +8 -0
  152. package/dist/src/source/line-comment-scanner.d.ts +1 -0
  153. package/dist/src/source/line-comment-scanner.js +51 -0
  154. package/dist/src/source/strip-line-comment.js +8 -44
  155. package/dist/src/syntax/directive-aliases.js +36 -22
  156. package/dist/src/syntax/expression-tokenizer.d.ts +30 -0
  157. package/dist/src/syntax/expression-tokenizer.js +310 -0
  158. package/dist/src/syntax/parse-directive-statement.d.ts +14 -0
  159. package/dist/src/syntax/parse-directive-statement.js +307 -0
  160. package/dist/src/syntax/parse-expression.d.ts +2 -2
  161. package/dist/src/syntax/parse-expression.js +7 -568
  162. package/dist/src/syntax/parse-layout-declarations.d.ts +9 -0
  163. package/dist/src/syntax/parse-layout-declarations.js +180 -0
  164. package/dist/src/syntax/parse-layout-expression.d.ts +5 -0
  165. package/dist/src/syntax/parse-layout-expression.js +175 -0
  166. package/dist/src/syntax/parse-line.js +4 -272
  167. package/dist/src/syntax/parse-token-expression.d.ts +3 -0
  168. package/dist/src/syntax/parse-token-expression.js +133 -0
  169. package/dist/src/tooling/case-style.js +47 -30
  170. package/dist/src/z80/effect-groups.d.ts +38 -0
  171. package/dist/src/z80/effect-groups.js +265 -0
  172. package/dist/src/z80/effect-units.d.ts +18 -0
  173. package/dist/src/z80/effect-units.js +165 -0
  174. package/dist/src/z80/effects.d.ts +1 -1
  175. package/dist/src/z80/effects.js +94 -557
  176. package/dist/src/z80/encode-core.d.ts +2 -0
  177. package/dist/src/z80/encode-core.js +42 -0
  178. package/dist/src/z80/encode-ld-helpers.d.ts +25 -0
  179. package/dist/src/z80/encode-ld-helpers.js +172 -0
  180. package/dist/src/z80/encode-ld.d.ts +2 -0
  181. package/dist/src/z80/encode-ld.js +285 -0
  182. package/dist/src/z80/encode.js +190 -542
  183. package/dist/src/z80/ld-support.d.ts +3 -0
  184. package/dist/src/z80/ld-support.js +146 -0
  185. package/dist/src/z80/operand-split-state.d.ts +8 -0
  186. package/dist/src/z80/operand-split-state.js +46 -0
  187. package/dist/src/z80/operand-split.d.ts +1 -0
  188. package/dist/src/z80/operand-split.js +13 -0
  189. package/dist/src/z80/parse-basic.d.ts +4 -0
  190. package/dist/src/z80/parse-basic.js +39 -0
  191. package/dist/src/z80/parse-branch.d.ts +4 -0
  192. package/dist/src/z80/parse-branch.js +218 -0
  193. package/dist/src/z80/parse-conditions.d.ts +6 -0
  194. package/dist/src/z80/parse-conditions.js +10 -0
  195. package/dist/src/z80/parse-exchange.d.ts +2 -0
  196. package/dist/src/z80/parse-exchange.js +30 -0
  197. package/dist/src/z80/parse-instruction.js +224 -1010
  198. package/dist/src/z80/parse-io-control.d.ts +5 -0
  199. package/dist/src/z80/parse-io-control.js +108 -0
  200. package/dist/src/z80/parse-ld.d.ts +2 -0
  201. package/dist/src/z80/parse-ld.js +83 -0
  202. package/dist/src/z80/parse-operands.d.ts +41 -0
  203. package/dist/src/z80/parse-operands.js +259 -0
  204. package/docs/reference/cli.md +42 -35
  205. package/docs/reference/tooling-api.md +20 -16
  206. package/package.json +1 -1
  207. package/dist/src/register-care/accept-output.d.ts +0 -2
  208. package/dist/src/register-care/analyze.js +0 -166
  209. package/dist/src/register-care/annotate.d.ts +0 -11
  210. package/dist/src/register-care/annotations.d.ts +0 -8
  211. package/dist/src/register-care/boundaryHints.d.ts +0 -3
  212. package/dist/src/register-care/boundaryHints.js +0 -80
  213. package/dist/src/register-care/carriers.d.ts +0 -2
  214. package/dist/src/register-care/controlFlow.d.ts +0 -5
  215. package/dist/src/register-care/controlFlow.js +0 -38
  216. package/dist/src/register-care/fix.d.ts +0 -11
  217. package/dist/src/register-care/instruction-shape.d.ts +0 -11
  218. package/dist/src/register-care/instruction-shape.js +0 -129
  219. package/dist/src/register-care/liveness.d.ts +0 -3
  220. package/dist/src/register-care/programModel.d.ts +0 -3
  221. package/dist/src/register-care/programModel.js +0 -266
  222. package/dist/src/register-care/report.d.ts +0 -5
  223. package/dist/src/register-care/routine-summaries.d.ts +0 -6
  224. package/dist/src/register-care/smartComments.d.ts +0 -5
  225. package/dist/src/register-care/smartComments.js +0 -243
  226. package/dist/src/register-care/summaries.d.ts +0 -12
  227. package/dist/src/register-care/summary.d.ts +0 -3
  228. package/dist/src/register-care/summary.js +0 -474
  229. package/dist/src/register-care/tooling.d.ts +0 -43
  230. package/dist/src/register-care/types.d.ts +0 -172
  231. /package/dist/src/{register-care → register-contracts}/accept-output.js +0 -0
  232. /package/dist/src/{register-care → register-contracts}/carriers.js +0 -0
  233. /package/dist/src/{register-care → register-contracts}/sourceText.d.ts +0 -0
  234. /package/dist/src/{register-care → register-contracts}/sourceText.js +0 -0
  235. /package/dist/src/{register-care → register-contracts}/types.js +0 -0
@@ -7,36 +7,53 @@ function unique(units) {
7
7
  }
8
8
  function boundaryTarget(routine, index, effect) {
9
9
  const item = routine.instructions[index];
10
- if (effect.control.kind === 'call' && effect.control.target) {
11
- return {
10
+ return (callBoundaryTarget(effect) ??
11
+ tailJumpBoundaryTarget(item, effect) ??
12
+ rstBoundaryTarget(routine, index, effect));
13
+ }
14
+ function callBoundaryTarget(effect) {
15
+ return effect.control.kind === 'call' && effect.control.target
16
+ ? {
12
17
  targets: [effect.control.target],
13
18
  conditional: effect.control.conditional,
14
19
  subject: `CALL ${effect.control.target}`,
15
- };
16
- }
17
- if (effect.control.kind === 'jump' &&
20
+ }
21
+ : undefined;
22
+ }
23
+ function tailJumpBoundaryTarget(item, effect) {
24
+ if (!isTailJumpBoundary(item, effect))
25
+ return undefined;
26
+ return {
27
+ targets: [effect.control.target],
28
+ conditional: false,
29
+ subject: `JP ${effect.control.target}`,
30
+ };
31
+ }
32
+ function isTailJumpBoundary(item, effect) {
33
+ return (effect.control.kind === 'jump' &&
18
34
  item?.instruction.mnemonic === 'jp' &&
19
35
  !effect.control.conditional &&
20
- effect.control.target &&
21
- !effect.control.target.startsWith('.')) {
22
- return {
23
- targets: [effect.control.target],
24
- conditional: false,
25
- subject: `JP ${effect.control.target}`,
26
- };
27
- }
28
- if (effect.control.kind === 'rst' && effect.control.vector !== undefined) {
29
- const target = rstTargetName(effect.control.vector);
30
- const previous = routine.instructions[index - 1];
31
- const service = precedingCServiceName(routine.instructions[index - 1]);
32
- const targets = [
33
- ...rstDispatcherServiceTargetNames(effect.control.vector, (register) => precedingRegisterImmediateValue(previous, register)),
34
- ...(service ? [rstServiceTargetName(effect.control.vector, service)] : []),
35
- target,
36
- ];
37
- return { targets, conditional: false, subject: target };
38
- }
39
- return undefined;
36
+ effect.control.target !== undefined &&
37
+ !effect.control.target.startsWith('.'));
38
+ }
39
+ function rstBoundaryTarget(routine, index, effect) {
40
+ if (effect.control.kind !== 'rst' || effect.control.vector === undefined)
41
+ return undefined;
42
+ const target = rstTargetName(effect.control.vector);
43
+ return {
44
+ targets: rstBoundaryTargets(routine, index, effect.control.vector, target),
45
+ conditional: false,
46
+ subject: target,
47
+ };
48
+ }
49
+ function rstBoundaryTargets(routine, index, vector, fallbackTarget) {
50
+ const previous = routine.instructions[index - 1];
51
+ const service = precedingCServiceName(previous);
52
+ return [
53
+ ...rstDispatcherServiceTargetNames(vector, (register) => precedingRegisterImmediateValue(previous, register)),
54
+ ...(service ? [rstServiceTargetName(vector, service)] : []),
55
+ fallbackTarget,
56
+ ];
40
57
  }
41
58
  function summaryForBoundary(boundary, summaries) {
42
59
  for (const target of boundary.targets) {
@@ -70,50 +87,59 @@ function unionLive(sets) {
70
87
  return out;
71
88
  }
72
89
  function transferLiveBefore(item, effect, boundary, summary, liveAfter, hints) {
90
+ const live = liveBeforeBoundary(item, boundary, summary, liveAfter, hints);
91
+ removeInstructionWrites(live, effect);
92
+ addInstructionReads(live, item, effect, liveAfter);
93
+ return live;
94
+ }
95
+ function liveBeforeBoundary(item, boundary, summary, liveAfter, hints) {
73
96
  const live = new Set(liveAfter);
74
97
  if (boundary && summary) {
75
- const accepted = new Set();
76
- for (const unit of hintUnitsForLine(hints, item.file, item.line))
77
- accepted.add(unit);
78
- for (const unit of outputUnits(summary))
79
- accepted.add(unit);
80
- if (!boundary.conditional) {
81
- for (const unit of summary.mayWrite)
82
- live.delete(unit);
83
- for (const unit of accepted)
84
- live.delete(unit);
85
- }
86
- for (const unit of summary.mayRead)
87
- live.add(unit);
98
+ applyBoundarySummary(live, item, boundary, summary, hints);
88
99
  }
89
- const instructionWritesAreConditional = effect.control.kind === 'call' && effect.control.conditional;
90
- if (!instructionWritesAreConditional) {
100
+ return live;
101
+ }
102
+ function applyBoundarySummary(live, item, boundary, summary, hints) {
103
+ const accepted = acceptedOutputUnits(item, summary, hints);
104
+ if (!boundary.conditional) {
105
+ for (const unit of summary.mayWrite)
106
+ live.delete(unit);
107
+ for (const unit of accepted)
108
+ live.delete(unit);
109
+ }
110
+ for (const unit of summary.mayRead)
111
+ live.add(unit);
112
+ }
113
+ function acceptedOutputUnits(item, summary, hints) {
114
+ return new Set([...hintUnitsForLine(hints, item.file, item.line), ...outputUnits(summary)]);
115
+ }
116
+ function removeInstructionWrites(live, effect) {
117
+ if (!instructionWritesAreConditional(effect)) {
91
118
  for (const unit of effect.writes)
92
119
  live.delete(unit);
93
120
  }
121
+ }
122
+ function instructionWritesAreConditional(effect) {
123
+ return effect.control.kind === 'call' && effect.control.conditional;
124
+ }
125
+ function addInstructionReads(live, item, effect, liveAfter) {
94
126
  for (const unit of semanticReadsForLiveness(item, effect, liveAfter))
95
127
  live.add(unit);
96
- return live;
97
128
  }
98
129
  function semanticReadsForLiveness(item, effect, liveAfter) {
99
- if (item.instruction.mnemonic === 'or' &&
100
- item.instruction.source.kind === 'reg8' &&
101
- item.instruction.source.register === 'a' &&
102
- !liveAfter.has('zero') &&
103
- !liveAfter.has('sign') &&
104
- !liveAfter.has('parity')) {
105
- return effect.reads.filter((unit) => unit !== 'A');
106
- }
107
- if (item.instruction.mnemonic === 'and' &&
108
- item.instruction.source.kind === 'reg8' &&
109
- item.instruction.source.register === 'a' &&
110
- !liveAfter.has('zero') &&
111
- !liveAfter.has('sign') &&
112
- !liveAfter.has('parity')) {
130
+ if (isAccumulatorFlagRefresh(item) && !hasAccumulatorDerivedFlags(liveAfter)) {
113
131
  return effect.reads.filter((unit) => unit !== 'A');
114
132
  }
115
133
  return effect.reads;
116
134
  }
135
+ function isAccumulatorFlagRefresh(item) {
136
+ return ((item.instruction.mnemonic === 'or' || item.instruction.mnemonic === 'and') &&
137
+ item.instruction.source.kind === 'reg8' &&
138
+ item.instruction.source.register === 'a');
139
+ }
140
+ function hasAccumulatorDerivedFlags(liveAfter) {
141
+ return liveAfter.has('zero') || liveAfter.has('sign') || liveAfter.has('parity');
142
+ }
117
143
  function liveSetsForRoutine(routine, summaries, hints = []) {
118
144
  const labels = labelIndex(routine);
119
145
  const effects = routine.instructions.map((item) => getZ80InstructionEffect(item.instruction));
@@ -157,7 +183,7 @@ function resolvedBoundariesForRoutine(routine, summaries) {
157
183
  }
158
184
  return out;
159
185
  }
160
- export function findRegisterCareConflicts(routine, summaries, hints) {
186
+ export function findRegisterContractsConflicts(routine, summaries, hints) {
161
187
  const conflicts = [];
162
188
  const { liveOut } = liveSetsForRoutine(routine, summaries, hints);
163
189
  for (const { item, index, boundary, target, summary } of resolvedBoundariesForRoutine(routine, summaries)) {
@@ -190,30 +216,36 @@ export function findCallerOutputCandidateObservations(routines, summaries) {
190
216
  for (const routine of routines) {
191
217
  const { liveOut } = liveSetsForRoutine(routine, summaries);
192
218
  for (const { item, index, boundary, target, summary } of resolvedBoundariesForRoutine(routine, summaries)) {
193
- const intentionalOutputs = new Set(outputUnits(summary));
194
- const carrierSources = [];
195
- for (const unit of summary.mayWrite) {
196
- if (liveOut[index].has(unit) && !intentionalOutputs.has(unit)) {
197
- carrierSources.push(unit);
198
- }
199
- }
200
- for (const unit of intentionalOutputs) {
201
- if (liveOut[index].has(unit)) {
202
- carrierSources.push(unit);
203
- }
204
- }
205
- const carriers = unique(carrierSources);
206
- if (carriers.length > 0) {
207
- out.push({
208
- file: item.file,
209
- line: item.line,
210
- column: item.column,
211
- routine: target,
212
- carriers,
213
- message: candidateMessage(boundary, carriers),
214
- });
215
- }
219
+ const candidate = callerOutputCandidate(item, boundary, target, summary, liveOut[index]);
220
+ if (candidate)
221
+ out.push(candidate);
216
222
  }
217
223
  }
218
224
  return out;
219
225
  }
226
+ function callerOutputCandidate(item, boundary, target, summary, liveAfter) {
227
+ const carriers = callerOutputCandidateCarriers(summary, liveAfter);
228
+ return carriers.length > 0
229
+ ? {
230
+ file: item.file,
231
+ line: item.line,
232
+ column: item.column,
233
+ routine: target,
234
+ carriers,
235
+ message: candidateMessage(boundary, carriers),
236
+ }
237
+ : undefined;
238
+ }
239
+ function callerOutputCandidateCarriers(summary, liveAfter) {
240
+ const intentionalOutputs = new Set(outputUnits(summary));
241
+ return unique([
242
+ ...unintentionalLiveWrites(summary, intentionalOutputs, liveAfter),
243
+ ...intentionalLiveOutputs(intentionalOutputs, liveAfter),
244
+ ]);
245
+ }
246
+ function unintentionalLiveWrites(summary, intentionalOutputs, liveAfter) {
247
+ return summary.mayWrite.filter((unit) => liveAfter.has(unit) && !intentionalOutputs.has(unit));
248
+ }
249
+ function intentionalLiveOutputs(intentionalOutputs, liveAfter) {
250
+ return [...intentionalOutputs].filter((unit) => liveAfter.has(unit));
251
+ }
@@ -0,0 +1,2 @@
1
+ import type { Z80Operand } from '../z80/instruction.js';
2
+ export declare function regName(operand: Z80Operand | undefined): string | undefined;
@@ -0,0 +1,13 @@
1
+ export function regName(operand) {
2
+ if (operand === undefined)
3
+ return undefined;
4
+ switch (operand.kind) {
5
+ case 'reg8':
6
+ case 'reg16':
7
+ case 'reg-index16':
8
+ case 'reg-half-index':
9
+ return operand.register.toUpperCase();
10
+ default:
11
+ return undefined;
12
+ }
13
+ }
@@ -1,14 +1,14 @@
1
- import type { RegisterCareUnit, RoutineSummary } from './types.js';
2
- export interface RegisterCareProfileSummary {
1
+ import type { RegisterContractsUnit, RoutineSummary } from './types.js';
2
+ export interface RegisterContractsProfileSummary {
3
3
  name: 'mon3';
4
4
  rst: Map<number, RoutineSummary>;
5
5
  rstServices: Map<string, RoutineSummary>;
6
6
  rstDispatchers: Map<number, {
7
- selector: RegisterCareUnit;
7
+ selector: RegisterContractsUnit;
8
8
  services: Map<number, RoutineSummary>;
9
9
  }>;
10
10
  }
11
11
  export declare function rstTargetName(vector: number): string;
12
12
  export declare function rstServiceTargetName(vector: number, service: string): string;
13
- export declare function rstDispatcherServiceTargetNames(vector: number, selectorValue: (register: RegisterCareUnit) => number | undefined): string[];
14
- export declare function getRegisterCareProfile(name: 'mon3' | undefined): RegisterCareProfileSummary | undefined;
13
+ export declare function rstDispatcherServiceTargetNames(vector: number, selectorValue: (register: RegisterContractsUnit) => number | undefined): string[];
14
+ export declare function getRegisterContractsProfile(name: 'mon3' | undefined): RegisterContractsProfileSummary | undefined;
@@ -95,7 +95,7 @@ function mon3ApiServices(overrides) {
95
95
  ]));
96
96
  }
97
97
  export function rstDispatcherServiceTargetNames(vector, selectorValue) {
98
- const mon3 = getRegisterCareProfile('mon3');
98
+ const mon3 = getRegisterContractsProfile('mon3');
99
99
  const dispatcher = mon3?.rstDispatchers.get(vector);
100
100
  if (dispatcher === undefined)
101
101
  return [];
@@ -105,7 +105,7 @@ export function rstDispatcherServiceTargetNames(vector, selectorValue) {
105
105
  const service = dispatcher.services.get(value);
106
106
  return service ? [service.name] : [];
107
107
  }
108
- export function getRegisterCareProfile(name) {
108
+ export function getRegisterContractsProfile(name) {
109
109
  if (name !== 'mon3')
110
110
  return undefined;
111
111
  const matrixScan = {
@@ -0,0 +1,6 @@
1
+ import type { SourceItem } from '../model/source-item.js';
2
+ import type { RegisterContractsDirectCall } from './types.js';
3
+ export declare function instructionCallTarget(item: SourceItem): string | undefined;
4
+ export declare function pushDirectBoundary(boundaries: RegisterContractsDirectCall[], target: string, subject: string, file: string, line: number, column: number): void;
5
+ export declare function collectFilesWithEntryLabels(items: readonly SourceItem[]): Set<string>;
6
+ export declare function collectDirectTailJumps(items: readonly SourceItem[], filesWithEntryLabels: ReadonlySet<string>): RegisterContractsDirectCall[];
@@ -0,0 +1,64 @@
1
+ function routineNameFromExpression(expression) {
2
+ return expression.kind === 'symbol' ? expression.name : undefined;
3
+ }
4
+ export function instructionCallTarget(item) {
5
+ if (item.kind !== 'instruction')
6
+ return undefined;
7
+ const mnemonic = item.instruction.mnemonic;
8
+ if (mnemonic === 'call' || mnemonic === 'call-cc') {
9
+ return routineNameFromExpression(item.instruction.expression);
10
+ }
11
+ return undefined;
12
+ }
13
+ function instructionTailJumpTarget(item, entryNames) {
14
+ if (item.kind !== 'instruction')
15
+ return undefined;
16
+ if (!isTailJumpInstruction(item.instruction, entryNames))
17
+ return undefined;
18
+ const target = routineNameFromExpression(item.instruction.expression);
19
+ return isEligibleTailJumpTarget(target, entryNames) ? target : undefined;
20
+ }
21
+ function isTailJumpInstruction(instruction, entryNames) {
22
+ return (instruction.mnemonic === 'jp' || (instruction.mnemonic === 'jp-cc' && entryNames !== undefined));
23
+ }
24
+ function isEligibleTailJumpTarget(target, entryNames) {
25
+ if (target === undefined || target.startsWith('.'))
26
+ return false;
27
+ return entryNames === undefined || entryNames.has(target);
28
+ }
29
+ export function pushDirectBoundary(boundaries, target, subject, file, line, column) {
30
+ boundaries.push({ target, subject, file, line, column });
31
+ }
32
+ export function collectFilesWithEntryLabels(items) {
33
+ return new Set(items
34
+ .filter((item) => item.kind === 'label')
35
+ .filter((item) => item.isEntry === true)
36
+ .map((item) => item.span.sourceName));
37
+ }
38
+ function entryNamesByFile(items) {
39
+ const namesByFile = new Map();
40
+ for (const item of items) {
41
+ if (item.kind !== 'label' || item.isEntry !== true)
42
+ continue;
43
+ const names = namesByFile.get(item.span.sourceName) ?? new Set();
44
+ names.add(item.name);
45
+ namesByFile.set(item.span.sourceName, names);
46
+ }
47
+ return namesByFile;
48
+ }
49
+ export function collectDirectTailJumps(items, filesWithEntryLabels) {
50
+ const entriesByFile = entryNamesByFile(items);
51
+ const directTailJumps = [];
52
+ for (const item of items) {
53
+ if (item.kind !== 'instruction')
54
+ continue;
55
+ const entryNames = filesWithEntryLabels.has(item.span.sourceName)
56
+ ? entriesByFile.get(item.span.sourceName)
57
+ : undefined;
58
+ const target = instructionTailJumpTarget(item, entryNames);
59
+ if (target === undefined)
60
+ continue;
61
+ pushDirectBoundary(directTailJumps, target, `JP ${target}`, item.span.sourceName, item.span.line, item.span.column);
62
+ }
63
+ return directTailJumps;
64
+ }
@@ -0,0 +1,7 @@
1
+ import type { SourceItem } from '../model/source-item.js';
2
+ import type { RegisterContractsDirectCall, RegisterContractsRoutine } from './types.js';
3
+ export interface RoutineBuildResult {
4
+ routines: RegisterContractsRoutine[];
5
+ directCalls: RegisterContractsDirectCall[];
6
+ }
7
+ export declare function buildRoutinesAndDirectCalls(items: readonly SourceItem[], constants: ReadonlyMap<string, number>, filesWithEntryLabels: ReadonlySet<string>): RoutineBuildResult;
@@ -0,0 +1,128 @@
1
+ import { instructionCallTarget, pushDirectBoundary } from './programModel-boundaries.js';
2
+ function isGlobalLabel(name) {
3
+ return !name.startsWith('.');
4
+ }
5
+ function emptyState() {
6
+ return {
7
+ entryLabels: [],
8
+ labels: [],
9
+ instructions: [],
10
+ };
11
+ }
12
+ function toInstruction(item, labels, constants) {
13
+ return {
14
+ instruction: item.instruction,
15
+ file: item.span.sourceName,
16
+ line: item.span.line,
17
+ column: item.span.column,
18
+ labels: [...labels],
19
+ constants,
20
+ };
21
+ }
22
+ function startRoutine(state, item) {
23
+ state.sourceName = item.span.sourceName;
24
+ state.routineName = item.name;
25
+ state.entryLabels = item.isEntry === true ? [item.name] : [];
26
+ state.labels = [item.name];
27
+ state.routineStartLine = item.span.line;
28
+ state.routineStartColumn = item.span.column;
29
+ state.instructions = [];
30
+ }
31
+ function routineSpan(state, end) {
32
+ const line = state.routineStartLine ?? 1;
33
+ return {
34
+ file: state.sourceName ?? '',
35
+ start: { line, column: state.routineStartColumn ?? 1 },
36
+ end: { line: end?.line ?? line, column: end?.column ?? state.routineStartColumn ?? 1 },
37
+ };
38
+ }
39
+ function flushRoutine(routines, state, constants) {
40
+ if (state.routineName === undefined || state.routineStartLine === undefined)
41
+ return;
42
+ const end = state.instructions[state.instructions.length - 1];
43
+ routines.push({
44
+ name: state.routineName,
45
+ labels: [...state.labels],
46
+ entryLabels: [...state.entryLabels],
47
+ instructions: [...state.instructions],
48
+ constants,
49
+ span: routineSpan(state, end),
50
+ });
51
+ }
52
+ function resetAndStart(routines, state, constants, item) {
53
+ flushRoutine(routines, state, constants);
54
+ Object.assign(state, emptyState());
55
+ startRoutine(state, item);
56
+ }
57
+ function appendDirectCall(directCalls, item) {
58
+ const directTarget = instructionCallTarget(item);
59
+ if (directTarget === undefined)
60
+ return;
61
+ pushDirectBoundary(directCalls, directTarget, `CALL ${directTarget}`, item.span.sourceName, item.span.line, item.span.column);
62
+ }
63
+ function handleInstruction(state, directCalls, item, constants) {
64
+ if (state.routineName === undefined || state.sourceName === undefined)
65
+ return;
66
+ if (item.span.sourceName !== state.sourceName)
67
+ return;
68
+ state.instructions.push(toInstruction(item, state.labels, constants));
69
+ appendDirectCall(directCalls, item);
70
+ }
71
+ function handleGlobalLabel(routines, state, item, constants, filesWithEntryLabels) {
72
+ if (state.routineName === undefined) {
73
+ if (shouldIgnoreNonEntryLabel(item, filesWithEntryLabels))
74
+ return;
75
+ startRoutine(state, item);
76
+ return;
77
+ }
78
+ if (isDifferentRoutineSource(state, item)) {
79
+ resetAndStart(routines, state, constants, item);
80
+ return;
81
+ }
82
+ if (state.instructions.length > 0) {
83
+ if (shouldKeepPostInstructionAlias(item, filesWithEntryLabels)) {
84
+ appendRoutineLabel(state, item);
85
+ return;
86
+ }
87
+ resetAndStart(routines, state, constants, item);
88
+ return;
89
+ }
90
+ appendRoutineLabel(state, item);
91
+ }
92
+ function shouldIgnoreNonEntryLabel(item, filesWithEntryLabels) {
93
+ return filesWithEntryLabels.has(item.span.sourceName) && item.isEntry !== true;
94
+ }
95
+ function isDifferentRoutineSource(state, item) {
96
+ return state.sourceName === undefined || state.sourceName !== item.span.sourceName;
97
+ }
98
+ function shouldKeepPostInstructionAlias(item, filesWithEntryLabels) {
99
+ return shouldIgnoreNonEntryLabel(item, filesWithEntryLabels);
100
+ }
101
+ function appendRoutineLabel(state, item) {
102
+ state.labels.push(item.name);
103
+ if (item.isEntry === true)
104
+ state.entryLabels.push(item.name);
105
+ }
106
+ function handleLabel(routines, state, item, constants, filesWithEntryLabels) {
107
+ if (!isGlobalLabel(item.name)) {
108
+ if (state.routineName !== undefined)
109
+ state.labels.push(item.name);
110
+ return;
111
+ }
112
+ handleGlobalLabel(routines, state, item, constants, filesWithEntryLabels);
113
+ }
114
+ export function buildRoutinesAndDirectCalls(items, constants, filesWithEntryLabels) {
115
+ const routines = [];
116
+ const directCalls = [];
117
+ const state = emptyState();
118
+ for (const item of items) {
119
+ if (item.kind === 'instruction') {
120
+ handleInstruction(state, directCalls, item, constants);
121
+ }
122
+ else if (item.kind === 'label') {
123
+ handleLabel(routines, state, item, constants, filesWithEntryLabels);
124
+ }
125
+ }
126
+ flushRoutine(routines, state, constants);
127
+ return { routines, directCalls };
128
+ }
@@ -0,0 +1,3 @@
1
+ import type { SourceItem } from '../model/source-item.js';
2
+ import type { RegisterContractsProgramModel } from './types.js';
3
+ export declare function buildRegisterContractsProgramModel(items: readonly SourceItem[]): RegisterContractsProgramModel;
@@ -0,0 +1,14 @@
1
+ import { collectConstants } from './constants.js';
2
+ import { collectDirectTailJumps, collectFilesWithEntryLabels } from './programModel-boundaries.js';
3
+ import { buildRoutinesAndDirectCalls } from './programModel-routines.js';
4
+ export function buildRegisterContractsProgramModel(items) {
5
+ const constants = collectConstants(items);
6
+ const filesWithEntryLabels = collectFilesWithEntryLabels(items);
7
+ const { routines, directCalls } = buildRoutinesAndDirectCalls(items, constants, filesWithEntryLabels);
8
+ const directTailJumps = collectDirectTailJumps(items, filesWithEntryLabels);
9
+ return {
10
+ routines,
11
+ directCalls,
12
+ directBoundaries: [...directCalls, ...directTailJumps],
13
+ };
14
+ }
@@ -0,0 +1,5 @@
1
+ import type { RegisterContractsReportModel, RegisterContractsUnit, RoutineSummary } from './types.js';
2
+ export declare function contractCarrierList(units: RegisterContractsUnit[]): string;
3
+ export declare function renderRegisterContractsReport(model: RegisterContractsReportModel): string;
4
+ export declare function renderRegisterContractsInterface(summaries: RoutineSummary[]): string;
5
+ export declare function renderRegisterContractsSourceBlock(summary: RoutineSummary): string[];
@@ -71,27 +71,41 @@ function stackStatus(summary) {
71
71
  function relationOutUnits(summary) {
72
72
  return new Set(summary.valueRelations.flatMap((rel) => rel.out));
73
73
  }
74
- export function renderRegisterCareReport(model) {
75
- const lines = ['AZM Register-Care Report', `Entry: ${model.entryFile}`, `Mode: ${model.mode}`];
74
+ export function renderRegisterContractsReport(model) {
75
+ const lines = [
76
+ 'AZM Register Contracts Report',
77
+ `Entry: ${model.entryFile}`,
78
+ `Mode: ${model.mode}`,
79
+ ];
76
80
  if (model.profile)
77
81
  lines.push(`Profile: ${model.profile}`);
78
82
  lines.push('');
83
+ appendRoutineSummaries(lines, model);
84
+ appendConflicts(lines, model);
85
+ appendOutputCandidates(lines, model);
86
+ appendUnknownCalls(lines, model);
87
+ return `${lines.join('\n')}\n`;
88
+ }
89
+ function appendRoutineSummaries(lines, model) {
79
90
  if (model.summaries.length === 0) {
80
91
  lines.push('Routines: none', '');
92
+ return;
81
93
  }
82
- else {
83
- for (const summary of model.summaries) {
84
- lines.push(`Routine: ${summary.name}`);
85
- lines.push(` reads: ${list(summary.mayRead)}`);
86
- lines.push(` writes: ${list(summary.mayWrite)}`);
87
- lines.push(` preserves: ${list(summary.preserved)}`);
88
- lines.push(` stack: ${stackStatus(summary)}`);
89
- for (const rel of summary.valueRelations) {
90
- lines.push(` relation: ${list(rel.out)} <= ${list(rel.from)}`);
91
- }
92
- lines.push('');
93
- }
94
+ for (const summary of model.summaries)
95
+ appendRoutineSummary(lines, summary);
96
+ }
97
+ function appendRoutineSummary(lines, summary) {
98
+ lines.push(`Routine: ${summary.name}`);
99
+ lines.push(` reads: ${list(summary.mayRead)}`);
100
+ lines.push(` writes: ${list(summary.mayWrite)}`);
101
+ lines.push(` preserves: ${list(summary.preserved)}`);
102
+ lines.push(` stack: ${stackStatus(summary)}`);
103
+ for (const rel of summary.valueRelations) {
104
+ lines.push(` relation: ${list(rel.out)} <= ${list(rel.from)}`);
94
105
  }
106
+ lines.push('');
107
+ }
108
+ function appendConflicts(lines, model) {
95
109
  lines.push('Conflicts:');
96
110
  if (model.conflicts.length === 0) {
97
111
  lines.push(' none');
@@ -102,6 +116,8 @@ export function renderRegisterCareReport(model) {
102
116
  }
103
117
  }
104
118
  lines.push('');
119
+ }
120
+ function appendOutputCandidates(lines, model) {
105
121
  lines.push('Output candidates:');
106
122
  if (!model.outputCandidates || model.outputCandidates.length === 0) {
107
123
  lines.push(' none');
@@ -112,6 +128,8 @@ export function renderRegisterCareReport(model) {
112
128
  }
113
129
  }
114
130
  lines.push('');
131
+ }
132
+ function appendUnknownCalls(lines, model) {
115
133
  lines.push('Unknown calls:');
116
134
  if (model.unknownCalls.length === 0) {
117
135
  lines.push(' none');
@@ -121,9 +139,8 @@ export function renderRegisterCareReport(model) {
121
139
  lines.push(` ${call}`);
122
140
  }
123
141
  lines.push('');
124
- return `${lines.join('\n')}\n`;
125
142
  }
126
- export function renderRegisterCareInterface(summaries) {
143
+ export function renderRegisterContractsInterface(summaries) {
127
144
  const lines = [];
128
145
  for (const summary of summaries) {
129
146
  lines.push(`extern ${summary.name}`);
@@ -134,6 +151,6 @@ export function renderRegisterCareInterface(summaries) {
134
151
  }
135
152
  return `${lines.join('\n')}\n`;
136
153
  }
137
- export function renderRegisterCareSourceBlock(summary) {
154
+ export function renderRegisterContractsSourceBlock(summary) {
138
155
  return sourceContractEntries(summary).map((entry) => `;! ${entry.keyword.padEnd(10)}${entry.carriers}`);
139
156
  }
@@ -0,0 +1,6 @@
1
+ import type { RegisterContractsRoutine, RoutineContract, RoutineSummary } from './types.js';
2
+ export declare function summariesWithExternalContracts(summaries: RoutineSummary[], contracts: Map<string, RoutineContract>, routineNameSet: Set<string>): RoutineSummary[];
3
+ export declare function inferRoutineSummariesToFixedPoint(routines: RegisterContractsRoutine[], contracts: Map<string, RoutineContract>, routineNameSet: Set<string>, profileSummaries: RoutineSummary[]): Array<{
4
+ routine: RegisterContractsRoutine;
5
+ summary: RoutineSummary;
6
+ }>;
@@ -45,6 +45,16 @@ function buildBoundarySummaryMap(summaries, routineSummaries, profileSummaries)
45
45
  }
46
46
  return boundarySummaryMap;
47
47
  }
48
+ function buildOptimisticInternalBoundarySummaryMap(routines) {
49
+ const out = new Map();
50
+ for (const routine of routines) {
51
+ const summary = emptyRoutineSummary(routine.name);
52
+ for (const label of boundaryLabels(routine)) {
53
+ out.set(label, summary);
54
+ }
55
+ }
56
+ return out;
57
+ }
48
58
  function summarizeRoutines(routines, contracts, boundarySummaryMap = new Map()) {
49
59
  return routines.map((routine) => {
50
60
  const inferred = inferRoutineSummary(routine, boundarySummaryMap);
@@ -73,7 +83,7 @@ function routineSummariesFingerprint(routineSummaries) {
73
83
  return routineSummaries.map((item) => summaryFingerprint(item.summary)).join('\n');
74
84
  }
75
85
  export function inferRoutineSummariesToFixedPoint(routines, contracts, routineNameSet, profileSummaries) {
76
- let routineSummaries = summarizeRoutines(routines, contracts);
86
+ let routineSummaries = summarizeRoutines(routines, contracts, buildOptimisticInternalBoundarySummaryMap(routines));
77
87
  const maxPasses = Math.max(2, routines.length + 2);
78
88
  for (let pass = 0; pass < maxPasses; pass += 1) {
79
89
  const summaries = summariesWithExternalContracts(routineSummaries.map((item) => item.summary), contracts, routineNameSet);
@@ -0,0 +1,5 @@
1
+ import type { LocatedSmartComment, RegisterContractsRoutine } from './types.js';
2
+ export declare function collectPrecedingCommentBlock(routine: RegisterContractsRoutine, sourceTexts: ReadonlyMap<string, string>): {
3
+ comments: LocatedSmartComment[];
4
+ complete: boolean;
5
+ };