@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.
- package/README.md +170 -69
- package/dist/src/api-artifacts.d.ts +20 -0
- package/dist/src/api-artifacts.js +165 -0
- package/dist/src/api-compile.d.ts +8 -2
- package/dist/src/api-compile.js +31 -230
- package/dist/src/api-register-contracts.d.ts +9 -0
- package/dist/src/api-register-contracts.js +77 -0
- package/dist/src/api-tooling.d.ts +2 -2
- package/dist/src/api-tooling.js +1 -1
- package/dist/src/assembly/address-planning.d.ts +1 -2
- package/dist/src/assembly/address-planning.js +119 -218
- package/dist/src/assembly/address-symbols.d.ts +12 -0
- package/dist/src/assembly/address-symbols.js +118 -0
- package/dist/src/assembly/fixup-emission.js +30 -48
- package/dist/src/assembly/program-emission.js +163 -164
- package/dist/src/cli/artifact-files.d.ts +15 -0
- package/dist/src/cli/artifact-files.js +86 -0
- package/dist/src/cli/parse-args.d.ts +6 -5
- package/dist/src/cli/parse-args.js +162 -136
- package/dist/src/cli/run.js +4 -1
- package/dist/src/cli/usage.d.ts +1 -0
- package/dist/src/cli/usage.js +33 -0
- package/dist/src/cli/write-artifacts.js +18 -91
- package/dist/src/core/compile.js +51 -274
- package/dist/src/core/conditional-assembly.d.ts +6 -0
- package/dist/src/core/conditional-assembly.js +181 -0
- package/dist/src/expansion/op-constant-expression.d.ts +3 -0
- package/dist/src/expansion/op-constant-expression.js +52 -0
- package/dist/src/expansion/op-expand-selected.d.ts +5 -0
- package/dist/src/expansion/op-expand-selected.js +143 -0
- package/dist/src/expansion/op-expansion.d.ts +5 -53
- package/dist/src/expansion/op-expansion.js +85 -815
- package/dist/src/expansion/op-instruction-instantiation.d.ts +3 -0
- package/dist/src/expansion/op-instruction-instantiation.js +194 -0
- package/dist/src/expansion/op-local-labels.d.ts +8 -0
- package/dist/src/expansion/op-local-labels.js +166 -0
- package/dist/src/expansion/op-operand-splitting.d.ts +1 -0
- package/dist/src/expansion/op-operand-splitting.js +44 -0
- package/dist/src/expansion/op-operands.d.ts +53 -0
- package/dist/src/expansion/op-operands.js +66 -0
- package/dist/src/expansion/op-selection.d.ts +18 -0
- package/dist/src/expansion/op-selection.js +172 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/model/diagnostic.d.ts +4 -0
- package/dist/src/model/diagnostic.js +4 -0
- package/dist/src/outputs/asm80-expression-evaluation.d.ts +10 -0
- package/dist/src/outputs/asm80-expression-evaluation.js +75 -0
- package/dist/src/outputs/asm80-expressions.d.ts +5 -0
- package/dist/src/outputs/asm80-expressions.js +47 -0
- package/dist/src/outputs/asm80-instruction-operands.d.ts +16 -0
- package/dist/src/outputs/asm80-instruction-operands.js +38 -0
- package/dist/src/outputs/asm80-instructions.d.ts +5 -0
- package/dist/src/outputs/asm80-instructions.js +272 -0
- package/dist/src/outputs/asm80-ld-operands.d.ts +10 -0
- package/dist/src/outputs/asm80-ld-operands.js +157 -0
- package/dist/src/outputs/asm80-strings.d.ts +4 -0
- package/dist/src/outputs/asm80-strings.js +14 -0
- package/dist/src/outputs/d8-files.d.ts +10 -0
- package/dist/src/outputs/d8-files.js +103 -0
- package/dist/src/outputs/d8-helpers.d.ts +21 -0
- package/dist/src/outputs/d8-helpers.js +136 -0
- package/dist/src/outputs/hex.js +26 -18
- package/dist/src/outputs/types.d.ts +16 -10
- package/dist/src/outputs/write-asm80.js +68 -597
- package/dist/src/outputs/write-d8.js +6 -216
- package/dist/src/register-contracts/accept-output.d.ts +2 -0
- package/dist/src/register-contracts/analyze-helpers.d.ts +29 -0
- package/dist/src/register-contracts/analyze-helpers.js +162 -0
- package/dist/src/{register-care → register-contracts}/analyze.d.ts +6 -6
- package/dist/src/register-contracts/analyze.js +73 -0
- package/dist/src/register-contracts/annotate.d.ts +11 -0
- package/dist/src/{register-care → register-contracts}/annotate.js +3 -3
- package/dist/src/register-contracts/annotations.d.ts +8 -0
- package/dist/src/{register-care → register-contracts}/annotations.js +3 -3
- package/dist/src/register-contracts/boundaryHints.d.ts +3 -0
- package/dist/src/register-contracts/boundaryHints.js +24 -0
- package/dist/src/register-contracts/carriers.d.ts +2 -0
- package/dist/src/register-contracts/constants.d.ts +4 -0
- package/dist/src/register-contracts/constants.js +51 -0
- package/dist/src/register-contracts/controlFlow.d.ts +5 -0
- package/dist/src/register-contracts/controlFlow.js +55 -0
- package/dist/src/register-contracts/fix.d.ts +11 -0
- package/dist/src/{register-care → register-contracts}/fix.js +47 -30
- package/dist/src/register-contracts/instruction-head.d.ts +2 -0
- package/dist/src/register-contracts/instruction-head.js +3 -0
- package/dist/src/register-contracts/instruction-operands.d.ts +3 -0
- package/dist/src/register-contracts/instruction-operands.js +101 -0
- package/dist/src/register-contracts/instruction-predicates.d.ts +6 -0
- package/dist/src/register-contracts/instruction-predicates.js +44 -0
- package/dist/src/register-contracts/interfaceContracts.d.ts +2 -0
- package/dist/src/register-contracts/interfaceContracts.js +68 -0
- package/dist/src/register-contracts/liveness.d.ts +3 -0
- package/dist/src/{register-care → register-contracts}/liveness.js +111 -79
- package/dist/src/register-contracts/operand-register-name.d.ts +2 -0
- package/dist/src/register-contracts/operand-register-name.js +13 -0
- package/dist/src/{register-care → register-contracts}/profiles.d.ts +5 -5
- package/dist/src/{register-care → register-contracts}/profiles.js +2 -2
- package/dist/src/register-contracts/programModel-boundaries.d.ts +6 -0
- package/dist/src/register-contracts/programModel-boundaries.js +64 -0
- package/dist/src/register-contracts/programModel-routines.d.ts +7 -0
- package/dist/src/register-contracts/programModel-routines.js +128 -0
- package/dist/src/register-contracts/programModel.d.ts +3 -0
- package/dist/src/register-contracts/programModel.js +14 -0
- package/dist/src/register-contracts/report.d.ts +5 -0
- package/dist/src/{register-care → register-contracts}/report.js +34 -17
- package/dist/src/register-contracts/routine-summaries.d.ts +6 -0
- package/dist/src/{register-care → register-contracts}/routine-summaries.js +11 -1
- package/dist/src/register-contracts/smartCommentBlocks.d.ts +5 -0
- package/dist/src/register-contracts/smartCommentBlocks.js +30 -0
- package/dist/src/register-contracts/smartCommentParsing.d.ts +3 -0
- package/dist/src/register-contracts/smartCommentParsing.js +80 -0
- package/dist/src/register-contracts/smartComments.d.ts +5 -0
- package/dist/src/register-contracts/smartComments.js +92 -0
- package/dist/src/register-contracts/summaries.d.ts +12 -0
- package/dist/src/{register-care → register-contracts}/summaries.js +7 -7
- package/dist/src/register-contracts/summary-boundary.d.ts +2 -0
- package/dist/src/register-contracts/summary-boundary.js +40 -0
- package/dist/src/register-contracts/summary-contract.d.ts +2 -0
- package/dist/src/register-contracts/summary-contract.js +45 -0
- package/dist/src/register-contracts/summary-result.d.ts +7 -0
- package/dist/src/register-contracts/summary-result.js +122 -0
- package/dist/src/register-contracts/summary-state.d.ts +23 -0
- package/dist/src/register-contracts/summary-state.js +88 -0
- package/dist/src/register-contracts/summary-token-transfer.d.ts +3 -0
- package/dist/src/register-contracts/summary-token-transfer.js +67 -0
- package/dist/src/register-contracts/summary.d.ts +3 -0
- package/dist/src/register-contracts/summary.js +266 -0
- package/dist/src/register-contracts/tooling.d.ts +57 -0
- package/dist/src/{register-care → register-contracts}/tooling.js +8 -6
- package/dist/src/register-contracts/types.d.ts +188 -0
- package/dist/src/semantics/binary-operators.d.ts +2 -0
- package/dist/src/semantics/binary-operators.js +15 -0
- package/dist/src/semantics/byte-functions.d.ts +2 -0
- package/dist/src/semantics/byte-functions.js +7 -0
- package/dist/src/semantics/constant-operator-types.d.ts +10 -0
- package/dist/src/semantics/constant-operator-types.js +1 -0
- package/dist/src/semantics/constant-operators.d.ts +3 -0
- package/dist/src/semantics/constant-operators.js +3 -0
- package/dist/src/semantics/diagnostics.d.ts +3 -0
- package/dist/src/semantics/diagnostics.js +10 -0
- package/dist/src/semantics/expression-evaluation.d.ts +11 -19
- package/dist/src/semantics/expression-evaluation.js +22 -334
- package/dist/src/semantics/layout-evaluation.d.ts +23 -0
- package/dist/src/semantics/layout-evaluation.js +202 -0
- package/dist/src/semantics/layout-format.d.ts +5 -0
- package/dist/src/semantics/layout-format.js +31 -0
- package/dist/src/semantics/layout-path.d.ts +24 -0
- package/dist/src/semantics/layout-path.js +58 -0
- package/dist/src/semantics/unary-operators.d.ts +2 -0
- package/dist/src/semantics/unary-operators.js +8 -0
- package/dist/src/source/line-comment-scanner.d.ts +1 -0
- package/dist/src/source/line-comment-scanner.js +51 -0
- package/dist/src/source/strip-line-comment.js +8 -44
- package/dist/src/syntax/directive-aliases.js +36 -22
- package/dist/src/syntax/expression-tokenizer.d.ts +30 -0
- package/dist/src/syntax/expression-tokenizer.js +310 -0
- package/dist/src/syntax/parse-directive-statement.d.ts +14 -0
- package/dist/src/syntax/parse-directive-statement.js +307 -0
- package/dist/src/syntax/parse-expression.d.ts +2 -2
- package/dist/src/syntax/parse-expression.js +7 -568
- package/dist/src/syntax/parse-layout-declarations.d.ts +9 -0
- package/dist/src/syntax/parse-layout-declarations.js +180 -0
- package/dist/src/syntax/parse-layout-expression.d.ts +5 -0
- package/dist/src/syntax/parse-layout-expression.js +175 -0
- package/dist/src/syntax/parse-line.js +4 -272
- package/dist/src/syntax/parse-token-expression.d.ts +3 -0
- package/dist/src/syntax/parse-token-expression.js +133 -0
- package/dist/src/tooling/case-style.js +47 -30
- package/dist/src/z80/effect-groups.d.ts +38 -0
- package/dist/src/z80/effect-groups.js +265 -0
- package/dist/src/z80/effect-units.d.ts +18 -0
- package/dist/src/z80/effect-units.js +165 -0
- package/dist/src/z80/effects.d.ts +1 -1
- package/dist/src/z80/effects.js +94 -557
- package/dist/src/z80/encode-core.d.ts +2 -0
- package/dist/src/z80/encode-core.js +42 -0
- package/dist/src/z80/encode-ld-helpers.d.ts +25 -0
- package/dist/src/z80/encode-ld-helpers.js +172 -0
- package/dist/src/z80/encode-ld.d.ts +2 -0
- package/dist/src/z80/encode-ld.js +285 -0
- package/dist/src/z80/encode.js +190 -542
- package/dist/src/z80/ld-support.d.ts +3 -0
- package/dist/src/z80/ld-support.js +146 -0
- package/dist/src/z80/operand-split-state.d.ts +8 -0
- package/dist/src/z80/operand-split-state.js +46 -0
- package/dist/src/z80/operand-split.d.ts +1 -0
- package/dist/src/z80/operand-split.js +13 -0
- package/dist/src/z80/parse-basic.d.ts +4 -0
- package/dist/src/z80/parse-basic.js +39 -0
- package/dist/src/z80/parse-branch.d.ts +4 -0
- package/dist/src/z80/parse-branch.js +218 -0
- package/dist/src/z80/parse-conditions.d.ts +6 -0
- package/dist/src/z80/parse-conditions.js +10 -0
- package/dist/src/z80/parse-exchange.d.ts +2 -0
- package/dist/src/z80/parse-exchange.js +30 -0
- package/dist/src/z80/parse-instruction.js +224 -1010
- package/dist/src/z80/parse-io-control.d.ts +5 -0
- package/dist/src/z80/parse-io-control.js +108 -0
- package/dist/src/z80/parse-ld.d.ts +2 -0
- package/dist/src/z80/parse-ld.js +83 -0
- package/dist/src/z80/parse-operands.d.ts +41 -0
- package/dist/src/z80/parse-operands.js +259 -0
- package/docs/reference/cli.md +42 -35
- package/docs/reference/tooling-api.md +20 -16
- package/package.json +1 -1
- package/dist/src/register-care/accept-output.d.ts +0 -2
- package/dist/src/register-care/analyze.js +0 -166
- package/dist/src/register-care/annotate.d.ts +0 -11
- package/dist/src/register-care/annotations.d.ts +0 -8
- package/dist/src/register-care/boundaryHints.d.ts +0 -3
- package/dist/src/register-care/boundaryHints.js +0 -80
- package/dist/src/register-care/carriers.d.ts +0 -2
- package/dist/src/register-care/controlFlow.d.ts +0 -5
- package/dist/src/register-care/controlFlow.js +0 -38
- package/dist/src/register-care/fix.d.ts +0 -11
- package/dist/src/register-care/instruction-shape.d.ts +0 -11
- package/dist/src/register-care/instruction-shape.js +0 -129
- package/dist/src/register-care/liveness.d.ts +0 -3
- package/dist/src/register-care/programModel.d.ts +0 -3
- package/dist/src/register-care/programModel.js +0 -266
- package/dist/src/register-care/report.d.ts +0 -5
- package/dist/src/register-care/routine-summaries.d.ts +0 -6
- package/dist/src/register-care/smartComments.d.ts +0 -5
- package/dist/src/register-care/smartComments.js +0 -243
- package/dist/src/register-care/summaries.d.ts +0 -12
- package/dist/src/register-care/summary.d.ts +0 -3
- package/dist/src/register-care/summary.js +0 -474
- package/dist/src/register-care/tooling.d.ts +0 -43
- package/dist/src/register-care/types.d.ts +0 -172
- /package/dist/src/{register-care → register-contracts}/accept-output.js +0 -0
- /package/dist/src/{register-care → register-contracts}/carriers.js +0 -0
- /package/dist/src/{register-care → register-contracts}/sourceText.d.ts +0 -0
- /package/dist/src/{register-care → register-contracts}/sourceText.js +0 -0
- /package/dist/src/{register-care → register-contracts}/types.js +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { applyBinaryOperator, applyByteFunction, applyUnaryOperator, } from '../semantics/constant-operators.js';
|
|
2
|
+
export function evaluateKnownConstant(expression, constants) {
|
|
3
|
+
switch (expression.kind) {
|
|
4
|
+
case 'number':
|
|
5
|
+
return expression.value;
|
|
6
|
+
case 'symbol':
|
|
7
|
+
return constants.get(expression.name);
|
|
8
|
+
case 'unary':
|
|
9
|
+
return evaluateUnaryConstant(expression, constants);
|
|
10
|
+
case 'binary':
|
|
11
|
+
return evaluateBinaryConstant(expression, constants);
|
|
12
|
+
case 'byte-function': {
|
|
13
|
+
const value = evaluateKnownConstant(expression.expression, constants);
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
return applyByteFunction(expression.function, value);
|
|
17
|
+
}
|
|
18
|
+
default:
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function collectConstants(items) {
|
|
23
|
+
const constants = new Map();
|
|
24
|
+
let changed = true;
|
|
25
|
+
while (changed) {
|
|
26
|
+
changed = false;
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
if (item.kind !== 'equ' || constants.has(item.name))
|
|
29
|
+
continue;
|
|
30
|
+
const value = evaluateKnownConstant(item.expression, constants);
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
constants.set(item.name, value);
|
|
34
|
+
changed = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return constants;
|
|
38
|
+
}
|
|
39
|
+
function evaluateUnaryConstant(expression, constants) {
|
|
40
|
+
const value = evaluateKnownConstant(expression.expression, constants);
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
return applyUnaryOperator(expression.operator, value);
|
|
44
|
+
}
|
|
45
|
+
function evaluateBinaryConstant(expression, constants) {
|
|
46
|
+
const left = evaluateKnownConstant(expression.left, constants);
|
|
47
|
+
const right = evaluateKnownConstant(expression.right, constants);
|
|
48
|
+
if (left === undefined || right === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
return applyBinaryOperator(expression.operator, left, right);
|
|
51
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { InstructionEffect, RegisterContractsRoutine } from './types.js';
|
|
2
|
+
export declare function labelIndex(routine: RegisterContractsRoutine): Map<string, number>;
|
|
3
|
+
export declare function instructionSuccessors(routine: RegisterContractsRoutine, index: number, effect: InstructionEffect, labels: ReadonlyMap<string, number>, options?: {
|
|
4
|
+
boundaryFallthrough?: boolean;
|
|
5
|
+
}): number[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function unique(items) {
|
|
2
|
+
return [...new Set(items)];
|
|
3
|
+
}
|
|
4
|
+
export function labelIndex(routine) {
|
|
5
|
+
const out = new Map();
|
|
6
|
+
routine.instructions.forEach((item, index) => {
|
|
7
|
+
for (const label of item.labels)
|
|
8
|
+
out.set(label, index);
|
|
9
|
+
});
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
function localTargetIndex(labels, target) {
|
|
13
|
+
if (!target)
|
|
14
|
+
return undefined;
|
|
15
|
+
return labels.get(target);
|
|
16
|
+
}
|
|
17
|
+
function maybeIndex(index) {
|
|
18
|
+
return index === undefined ? [] : [index];
|
|
19
|
+
}
|
|
20
|
+
function nextInstructionIndex(routine, index) {
|
|
21
|
+
return index + 1 < routine.instructions.length ? index + 1 : undefined;
|
|
22
|
+
}
|
|
23
|
+
function shouldFallThroughBoundary(control, boundaryFallthrough) {
|
|
24
|
+
return Boolean(boundaryFallthrough && (control.kind === 'call' || control.kind === 'rst'));
|
|
25
|
+
}
|
|
26
|
+
function jumpSuccessors(control, labels, next) {
|
|
27
|
+
const target = localTargetIndex(labels, control.target);
|
|
28
|
+
if (!control.conditional)
|
|
29
|
+
return maybeIndex(target);
|
|
30
|
+
return unique([...maybeIndex(target), ...maybeIndex(next)]);
|
|
31
|
+
}
|
|
32
|
+
function returnSuccessors(control, next) {
|
|
33
|
+
return control.conditional ? maybeIndex(next) : [];
|
|
34
|
+
}
|
|
35
|
+
function controlSuccessors(control, labels, next) {
|
|
36
|
+
switch (control.kind) {
|
|
37
|
+
case 'fallthrough':
|
|
38
|
+
return maybeIndex(next);
|
|
39
|
+
case 'jump':
|
|
40
|
+
return jumpSuccessors(control, labels, next);
|
|
41
|
+
case 'return':
|
|
42
|
+
return returnSuccessors(control, next);
|
|
43
|
+
case 'call':
|
|
44
|
+
case 'rst':
|
|
45
|
+
case 'unknown':
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function instructionSuccessors(routine, index, effect, labels, options = {}) {
|
|
50
|
+
const next = nextInstructionIndex(routine, index);
|
|
51
|
+
if (shouldFallThroughBoundary(effect.control, options.boundaryFallthrough)) {
|
|
52
|
+
return maybeIndex(next);
|
|
53
|
+
}
|
|
54
|
+
return controlSuccessors(effect.control, labels, next);
|
|
55
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RegisterContractsOutputCandidate, RegisterContractsRoutine, RegisterContractsUnit } from './types.js';
|
|
2
|
+
export interface RegisterContractsExpectOutFix {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
column: number;
|
|
6
|
+
routine: string;
|
|
7
|
+
carriers: RegisterContractsUnit[];
|
|
8
|
+
}
|
|
9
|
+
export declare function findExpectOutFixesForCandidates(routines: RegisterContractsRoutine[], candidates: RegisterContractsOutputCandidate[]): RegisterContractsExpectOutFix[];
|
|
10
|
+
export declare function autoFixableCandidateKeys(routines: RegisterContractsRoutine[], candidates: RegisterContractsOutputCandidate[]): Set<string>;
|
|
11
|
+
export declare function applyExpectOutFixesToSource(source: string, fixes: RegisterContractsExpectOutFix[], referenceSource?: string): string;
|
|
@@ -2,6 +2,15 @@ import { getZ80InstructionEffect } from '../z80/effects.js';
|
|
|
2
2
|
import { instructionSuccessors, labelIndex } from './controlFlow.js';
|
|
3
3
|
import { contractCarrierList } from './report.js';
|
|
4
4
|
import { joinSourceLines, splitSourceLines } from './sourceText.js';
|
|
5
|
+
const REGISTER_READ_UNITS = {
|
|
6
|
+
bc: ['B', 'C'],
|
|
7
|
+
de: ['D', 'E'],
|
|
8
|
+
hl: ['H', 'L'],
|
|
9
|
+
ix: ['IXH', 'IXL'],
|
|
10
|
+
iy: ['IYH', 'IYL'],
|
|
11
|
+
sp: ['SPH', 'SPL'],
|
|
12
|
+
af: ['A'],
|
|
13
|
+
};
|
|
5
14
|
function sameLocation(a, b) {
|
|
6
15
|
return a.file === b.file && a.line === b.line && a.column === b.column;
|
|
7
16
|
}
|
|
@@ -29,14 +38,7 @@ function operandReadsUnit(operand, unit) {
|
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
function registerNameReadsUnit(registerName, unit) {
|
|
32
|
-
|
|
33
|
-
return ((register === 'bc' && (unit === 'B' || unit === 'C')) ||
|
|
34
|
-
(register === 'de' && (unit === 'D' || unit === 'E')) ||
|
|
35
|
-
(register === 'hl' && (unit === 'H' || unit === 'L')) ||
|
|
36
|
-
(register === 'ix' && (unit === 'IXH' || unit === 'IXL')) ||
|
|
37
|
-
(register === 'iy' && (unit === 'IYH' || unit === 'IYL')) ||
|
|
38
|
-
(register === 'sp' && (unit === 'SPH' || unit === 'SPL')) ||
|
|
39
|
-
(register === 'af' && unit === 'A'));
|
|
41
|
+
return REGISTER_READ_UNITS[registerName.toLowerCase()]?.includes(unit) ?? false;
|
|
40
42
|
}
|
|
41
43
|
function instructionDataReadsUnit(instruction, unit) {
|
|
42
44
|
switch (instruction.mnemonic) {
|
|
@@ -58,36 +60,18 @@ function instructionDataReadsUnit(instruction, unit) {
|
|
|
58
60
|
function continuationReads(routine, callIndex, carriers) {
|
|
59
61
|
const labels = labelIndex(routine);
|
|
60
62
|
const confirmed = new Set();
|
|
61
|
-
const work = callIndex
|
|
62
|
-
? [{ index: callIndex + 1, pending: [...new Set(carriers)] }]
|
|
63
|
-
: [];
|
|
63
|
+
const work = initialContinuationWork(routine, callIndex, carriers);
|
|
64
64
|
const seen = new Set();
|
|
65
65
|
let steps = 0;
|
|
66
66
|
while (work.length > 0 && steps < 512) {
|
|
67
67
|
steps += 1;
|
|
68
68
|
const state = work.pop();
|
|
69
|
-
const pending = state.pending.filter((unit) => !confirmed.has(unit));
|
|
70
|
-
if (pending.length === 0)
|
|
71
|
-
continue;
|
|
72
|
-
const key = `${state.index}:${pending.join(',')}`;
|
|
73
|
-
if (seen.has(key))
|
|
74
|
-
continue;
|
|
75
|
-
seen.add(key);
|
|
76
69
|
const item = routine.instructions[state.index];
|
|
77
|
-
|
|
70
|
+
const pending = unseenContinuationPending(state, confirmed, seen);
|
|
71
|
+
if (!item || pending.length === 0)
|
|
78
72
|
continue;
|
|
79
73
|
const effect = getZ80InstructionEffect(item.instruction);
|
|
80
|
-
const
|
|
81
|
-
const writes = new Set(effect.writes);
|
|
82
|
-
const remaining = [];
|
|
83
|
-
for (const unit of pending) {
|
|
84
|
-
if (reads.has(unit) && instructionDataReadsUnit(item.instruction, unit)) {
|
|
85
|
-
confirmed.add(unit);
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (!writes.has(unit))
|
|
89
|
-
remaining.push(unit);
|
|
90
|
-
}
|
|
74
|
+
const remaining = remainingContinuationUnits(item, pending, confirmed);
|
|
91
75
|
if (remaining.length === 0)
|
|
92
76
|
continue;
|
|
93
77
|
for (const next of instructionSuccessors(routine, state.index, effect, labels)) {
|
|
@@ -96,6 +80,39 @@ function continuationReads(routine, callIndex, carriers) {
|
|
|
96
80
|
}
|
|
97
81
|
return carriers.filter((unit) => confirmed.has(unit));
|
|
98
82
|
}
|
|
83
|
+
function initialContinuationWork(routine, callIndex, carriers) {
|
|
84
|
+
return callIndex + 1 < routine.instructions.length
|
|
85
|
+
? [{ index: callIndex + 1, pending: [...new Set(carriers)] }]
|
|
86
|
+
: [];
|
|
87
|
+
}
|
|
88
|
+
function unseenContinuationPending(state, confirmed, seen) {
|
|
89
|
+
const pending = state.pending.filter((unit) => !confirmed.has(unit));
|
|
90
|
+
if (pending.length === 0)
|
|
91
|
+
return [];
|
|
92
|
+
const key = `${state.index}:${pending.join(',')}`;
|
|
93
|
+
if (seen.has(key))
|
|
94
|
+
return [];
|
|
95
|
+
seen.add(key);
|
|
96
|
+
return pending;
|
|
97
|
+
}
|
|
98
|
+
function remainingContinuationUnits(item, pending, confirmed) {
|
|
99
|
+
const effect = getZ80InstructionEffect(item.instruction);
|
|
100
|
+
const reads = new Set(effect.reads);
|
|
101
|
+
const writes = new Set(effect.writes);
|
|
102
|
+
const remaining = [];
|
|
103
|
+
for (const unit of pending) {
|
|
104
|
+
if (isConfirmedContinuationRead(item, unit, reads)) {
|
|
105
|
+
confirmed.add(unit);
|
|
106
|
+
}
|
|
107
|
+
else if (!writes.has(unit)) {
|
|
108
|
+
remaining.push(unit);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return remaining;
|
|
112
|
+
}
|
|
113
|
+
function isConfirmedContinuationRead(item, unit, reads) {
|
|
114
|
+
return reads.has(unit) && instructionDataReadsUnit(item.instruction, unit);
|
|
115
|
+
}
|
|
99
116
|
function findExpectOutFixes(routines, candidates) {
|
|
100
117
|
const out = [];
|
|
101
118
|
for (const routine of routines) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const OPERAND_COUNTS = {
|
|
2
|
+
ret: 0,
|
|
3
|
+
'ret-cc': 1,
|
|
4
|
+
jp: 1,
|
|
5
|
+
'jp-cc': 1,
|
|
6
|
+
jr: 1,
|
|
7
|
+
'jr-cc': 1,
|
|
8
|
+
djnz: 1,
|
|
9
|
+
call: 1,
|
|
10
|
+
'call-cc': 1,
|
|
11
|
+
sub: 1,
|
|
12
|
+
and: 1,
|
|
13
|
+
or: 1,
|
|
14
|
+
xor: 1,
|
|
15
|
+
cp: 1,
|
|
16
|
+
ld: 2,
|
|
17
|
+
ex: 2,
|
|
18
|
+
add: targetedAluOperandCount,
|
|
19
|
+
adc: targetedAluOperandCount,
|
|
20
|
+
sbc: targetedAluOperandCount,
|
|
21
|
+
};
|
|
22
|
+
const OPERAND_SELECTORS = {
|
|
23
|
+
ld: ldOperand,
|
|
24
|
+
ex: exOperand,
|
|
25
|
+
add: aluOperand,
|
|
26
|
+
adc: aluOperand,
|
|
27
|
+
sbc: aluOperand,
|
|
28
|
+
sub: sourceOperand,
|
|
29
|
+
and: sourceOperand,
|
|
30
|
+
or: sourceOperand,
|
|
31
|
+
xor: sourceOperand,
|
|
32
|
+
cp: sourceOperand,
|
|
33
|
+
};
|
|
34
|
+
export function instructionOperandCount(instruction) {
|
|
35
|
+
const count = OPERAND_COUNTS[instruction.mnemonic];
|
|
36
|
+
if (count === undefined)
|
|
37
|
+
return 0;
|
|
38
|
+
return typeof count === 'number' ? count : count(instruction);
|
|
39
|
+
}
|
|
40
|
+
export function instructionOperand(instruction, index) {
|
|
41
|
+
return OPERAND_SELECTORS[instruction.mnemonic]?.(instruction, index);
|
|
42
|
+
}
|
|
43
|
+
function targetedAluOperandCount(instruction) {
|
|
44
|
+
return 'target' in instruction ? 2 : 1;
|
|
45
|
+
}
|
|
46
|
+
function positionalOperand(index, target, source) {
|
|
47
|
+
if (index === 0)
|
|
48
|
+
return target;
|
|
49
|
+
if (index === 1)
|
|
50
|
+
return source;
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function ldOperand(instruction, index) {
|
|
54
|
+
return instruction.mnemonic === 'ld'
|
|
55
|
+
? positionalOperand(index, instruction.target, instruction.source)
|
|
56
|
+
: undefined;
|
|
57
|
+
}
|
|
58
|
+
function exOperand(instruction, index) {
|
|
59
|
+
if (instruction.mnemonic !== 'ex')
|
|
60
|
+
return undefined;
|
|
61
|
+
if (index === 0)
|
|
62
|
+
return firstExOperand(instruction.form);
|
|
63
|
+
if (index === 1)
|
|
64
|
+
return secondExOperand(instruction.form);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
function firstExOperand(form) {
|
|
68
|
+
if (form === 'de-hl')
|
|
69
|
+
return { kind: 'reg16', register: 'de' };
|
|
70
|
+
if (form === 'af-af')
|
|
71
|
+
return { kind: 'reg16', register: 'af' };
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
function secondExOperand(form) {
|
|
75
|
+
return form === 'de-hl' ? { kind: 'reg16', register: 'hl' } : undefined;
|
|
76
|
+
}
|
|
77
|
+
function aluOperand(instruction, index) {
|
|
78
|
+
if (instruction.mnemonic !== 'add' &&
|
|
79
|
+
instruction.mnemonic !== 'adc' &&
|
|
80
|
+
instruction.mnemonic !== 'sbc') {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
if ('target' in instruction) {
|
|
84
|
+
return positionalOperand(index, instruction.target, instruction.source);
|
|
85
|
+
}
|
|
86
|
+
return index === 0 ? instruction.source : undefined;
|
|
87
|
+
}
|
|
88
|
+
function sourceOperand(instruction, index) {
|
|
89
|
+
if (index !== 0)
|
|
90
|
+
return undefined;
|
|
91
|
+
switch (instruction.mnemonic) {
|
|
92
|
+
case 'sub':
|
|
93
|
+
case 'and':
|
|
94
|
+
case 'or':
|
|
95
|
+
case 'xor':
|
|
96
|
+
case 'cp':
|
|
97
|
+
return instruction.source;
|
|
98
|
+
default:
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RegisterContractsInstruction } from './types.js';
|
|
2
|
+
export declare function isUnconditionalReturnInstruction(item: RegisterContractsInstruction): boolean;
|
|
3
|
+
export declare function isPureTokenTransferInstruction(item: RegisterContractsInstruction): boolean;
|
|
4
|
+
export declare function isAccumulatorSelfOperand(item: RegisterContractsInstruction): boolean;
|
|
5
|
+
export declare function isImmediateZeroOperand(item: RegisterContractsInstruction): boolean;
|
|
6
|
+
export declare function isRegisterOperand(item: RegisterContractsInstruction | undefined, index: number, name: string): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { instructionOperand, instructionOperandCount } from './instruction-operands.js';
|
|
2
|
+
import { instructionHead } from './instruction-head.js';
|
|
3
|
+
import { regName } from './operand-register-name.js';
|
|
4
|
+
function immValue(item) {
|
|
5
|
+
const instruction = item.instruction;
|
|
6
|
+
if (instruction.mnemonic !== 'cp' || instruction.source.kind !== 'imm')
|
|
7
|
+
return undefined;
|
|
8
|
+
const expression = instruction.source.expression;
|
|
9
|
+
return expression.kind === 'number' ? expression.value : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function isUnconditionalReturnInstruction(item) {
|
|
12
|
+
const head = instructionHead(item);
|
|
13
|
+
if (head === 'ret')
|
|
14
|
+
return item.instruction.mnemonic === 'ret';
|
|
15
|
+
return head === 'retn' || head === 'reti';
|
|
16
|
+
}
|
|
17
|
+
export function isPureTokenTransferInstruction(item) {
|
|
18
|
+
const head = instructionHead(item);
|
|
19
|
+
if (head === 'ex')
|
|
20
|
+
return true;
|
|
21
|
+
if (head !== 'ld' || instructionOperandCount(item.instruction) !== 2)
|
|
22
|
+
return false;
|
|
23
|
+
const dst = instructionOperand(item.instruction, 0);
|
|
24
|
+
const src = instructionOperand(item.instruction, 1);
|
|
25
|
+
if (regName(dst) === undefined)
|
|
26
|
+
return false;
|
|
27
|
+
return regName(src) !== undefined || src?.kind === 'imm';
|
|
28
|
+
}
|
|
29
|
+
export function isAccumulatorSelfOperand(item) {
|
|
30
|
+
const inst = item.instruction;
|
|
31
|
+
if (inst.mnemonic === 'or' || inst.mnemonic === 'and' || inst.mnemonic === 'xor') {
|
|
32
|
+
return inst.source.kind === 'reg8' && inst.source.register === 'a';
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
export function isImmediateZeroOperand(item) {
|
|
37
|
+
return immValue(item) === 0;
|
|
38
|
+
}
|
|
39
|
+
export function isRegisterOperand(item, index, name) {
|
|
40
|
+
if (item === undefined)
|
|
41
|
+
return false;
|
|
42
|
+
const operand = instructionOperand(item.instruction, index);
|
|
43
|
+
return regName(operand) === name.toUpperCase();
|
|
44
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { expandCarrierList } from './carriers.js';
|
|
2
|
+
import { buildRoutineContracts } from './smartComments.js';
|
|
3
|
+
const INTERFACE_TAG_RE = /^\s*(in|out|clobbers|preserves)(?:\s+(.+))?$/i;
|
|
4
|
+
const INTERFACE_CONTRACT_BUILDERS = {
|
|
5
|
+
in: (carriers) => ({ kind: 'in', carriers }),
|
|
6
|
+
out: (carriers) => ({ kind: 'out', carriers }),
|
|
7
|
+
clobbers: (carriers) => ({ kind: 'clobbers', carriers }),
|
|
8
|
+
preserves: (carriers) => ({ kind: 'preserves', carriers }),
|
|
9
|
+
};
|
|
10
|
+
export function parseInterfaceContracts(text, file = '<register-contracts-interface>') {
|
|
11
|
+
const comments = [];
|
|
12
|
+
const lines = text.split(/\r?\n/u);
|
|
13
|
+
for (const [index, line] of lines.entries()) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (trimmed.length === 0)
|
|
16
|
+
continue;
|
|
17
|
+
if (trimmed.startsWith(';')) {
|
|
18
|
+
throw new Error(`${file}:${index + 1}: .asmi files do not permit comments`);
|
|
19
|
+
}
|
|
20
|
+
const comment = parseInterfaceContractLine(line);
|
|
21
|
+
if (comment === undefined) {
|
|
22
|
+
throw new Error(`${file}:${index + 1}: invalid register contracts interface line \"${trimmed}\"`);
|
|
23
|
+
}
|
|
24
|
+
comments.push({ file, line: index + 1, comment });
|
|
25
|
+
}
|
|
26
|
+
const routines = buildRoutineContracts(comments);
|
|
27
|
+
const out = new Map();
|
|
28
|
+
for (const [name, contract] of routines) {
|
|
29
|
+
if (hasContractContent(contract))
|
|
30
|
+
out.set(name, contract);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
function parseInterfaceContractLine(line) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (trimmed.length === 0 || trimmed.startsWith(';'))
|
|
37
|
+
return undefined;
|
|
38
|
+
const boundary = parseInterfaceBoundary(trimmed);
|
|
39
|
+
if (boundary !== undefined)
|
|
40
|
+
return boundary;
|
|
41
|
+
const match = INTERFACE_TAG_RE.exec(trimmed);
|
|
42
|
+
if (!match)
|
|
43
|
+
return undefined;
|
|
44
|
+
const tag = match[1].toLowerCase();
|
|
45
|
+
const carriers = parseInterfaceCarrierList(match[2]?.trim());
|
|
46
|
+
return carriers === undefined ? undefined : INTERFACE_CONTRACT_BUILDERS[tag](carriers);
|
|
47
|
+
}
|
|
48
|
+
function parseInterfaceBoundary(trimmed) {
|
|
49
|
+
const extern = /^extern\s+(\S+)\s*$/i.exec(trimmed);
|
|
50
|
+
if (extern !== null)
|
|
51
|
+
return { kind: 'extern', name: extern[1] };
|
|
52
|
+
return /^end\s*$/i.test(trimmed) ? { kind: 'end' } : undefined;
|
|
53
|
+
}
|
|
54
|
+
function parseInterfaceCarrierList(rest) {
|
|
55
|
+
if (!rest)
|
|
56
|
+
return undefined;
|
|
57
|
+
const rawCarriers = rest.split(',').map((part) => part.trim());
|
|
58
|
+
if (rawCarriers.length === 0 || rawCarriers.some((part) => part.length === 0))
|
|
59
|
+
return undefined;
|
|
60
|
+
const carriers = expandCarrierList(rawCarriers);
|
|
61
|
+
return carriers && carriers.length > 0 ? carriers : undefined;
|
|
62
|
+
}
|
|
63
|
+
function hasContractContent(contract) {
|
|
64
|
+
return (contract.in.length > 0 ||
|
|
65
|
+
contract.out.length > 0 ||
|
|
66
|
+
contract.clobbers.length > 0 ||
|
|
67
|
+
contract.preserves.length > 0);
|
|
68
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { LocatedSmartComment, RegisterContractsConflict, RegisterContractsOutputCandidate, RegisterContractsRoutine, RoutineSummary } from './types.js';
|
|
2
|
+
export declare function findRegisterContractsConflicts(routine: RegisterContractsRoutine, summaries: Map<string, RoutineSummary>, hints: LocatedSmartComment[]): RegisterContractsConflict[];
|
|
3
|
+
export declare function findCallerOutputCandidateObservations(routines: RegisterContractsRoutine[], summaries: Map<string, RoutineSummary>): RegisterContractsOutputCandidate[];
|