@jhlagado/azm 0.2.0 → 0.2.1
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 +95 -70
- package/dist/src/api-compile.js +1 -1
- package/dist/src/assembly/address-planning.js +2 -0
- package/dist/src/assembly/program-emission.js +1 -0
- package/dist/src/expansion/op-expansion.js +1 -0
- package/dist/src/model/source-item.d.ts +6 -0
- package/dist/src/outputs/write-asm80.js +122 -5
- package/dist/src/register-care/analyze.js +36 -8
- package/dist/src/register-care/annotate.d.ts +11 -0
- package/dist/src/register-care/annotate.js +76 -0
- package/dist/src/register-care/annotations.js +33 -146
- package/dist/src/register-care/fix.d.ts +2 -0
- package/dist/src/register-care/fix.js +52 -0
- package/dist/src/register-care/instruction-shape.d.ts +11 -0
- package/dist/src/register-care/instruction-shape.js +129 -0
- package/dist/src/register-care/liveness.js +15 -7
- package/dist/src/register-care/profiles.js +4 -0
- package/dist/src/register-care/programModel.js +79 -13
- package/dist/src/register-care/report.d.ts +2 -1
- package/dist/src/register-care/report.js +91 -34
- package/dist/src/register-care/routine-summaries.d.ts +6 -0
- package/dist/src/register-care/routine-summaries.js +89 -0
- package/dist/src/register-care/sourceText.d.ts +8 -0
- package/dist/src/register-care/sourceText.js +15 -0
- package/dist/src/register-care/summaries.d.ts +3 -3
- package/dist/src/register-care/summaries.js +42 -75
- package/dist/src/register-care/summary.d.ts +3 -0
- package/dist/src/register-care/summary.js +474 -0
- package/dist/src/register-care/types.d.ts +6 -1
- package/dist/src/source/strip-line-comment.d.ts +2 -0
- package/dist/src/source/strip-line-comment.js +26 -0
- package/dist/src/syntax/parse-diagnostics.d.ts +12 -0
- package/dist/src/syntax/parse-diagnostics.js +18 -0
- package/dist/src/syntax/parse-line.js +63 -10
- package/docs/reference/tooling-api.md +13 -6
- package/package.json +4 -2
|
@@ -1,154 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return units.length === 1 ? units[0] : `{${units.join(',')}}`;
|
|
6
|
-
}
|
|
7
|
-
function formatCarrierLineWithExpectOut(indentation, units) {
|
|
8
|
-
return `${indentation}; expects out ${formatCandidateUnits(units)}`;
|
|
9
|
-
}
|
|
10
|
-
function formatCarrierLineWithMaybeOut(indentation, units) {
|
|
11
|
-
return `${indentation};! ${'maybe-out'.padEnd(10)}${formatCandidateUnits(units)}`;
|
|
12
|
-
}
|
|
13
|
-
function isGeneratedRegisterContractLine(line) {
|
|
14
|
-
return /^\s*;!\s*(in|out|clobbers|preserves|maybe-out)\b/i.test(line);
|
|
15
|
-
}
|
|
16
|
-
function isOutputCandidateHintLine(line) {
|
|
17
|
-
return /^\s*;\s*expects\s+out\b/i.test(line) || /^\s*;\s*!\s*maybe-out\b/i.test(line);
|
|
18
|
-
}
|
|
19
|
-
function normalizeLineEnding(text) {
|
|
20
|
-
return text.replace(/\r\n/g, '\n');
|
|
21
|
-
}
|
|
22
|
-
function splitSourceLines(text) {
|
|
23
|
-
return normalizeLineEnding(text).split('\n');
|
|
24
|
-
}
|
|
25
|
-
function lineDeltaForCandidate(line, deltas) {
|
|
26
|
-
let shift = 0;
|
|
27
|
-
for (const delta of deltas) {
|
|
28
|
-
if (delta.anchorLine < line) {
|
|
29
|
-
shift += delta.delta;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return shift;
|
|
33
|
-
}
|
|
34
|
-
function applyOutputCandidateHints(sourceText, outputCandidates, candidateFixability, deltas, outputCandidateKey) {
|
|
35
|
-
const lines = splitSourceLines(sourceText);
|
|
36
|
-
const grouped = new Map();
|
|
37
|
-
for (const candidate of outputCandidates) {
|
|
38
|
-
const adjustedLine = candidate.line + lineDeltaForCandidate(candidate.line, deltas);
|
|
39
|
-
const existing = grouped.get(adjustedLine);
|
|
40
|
-
const autoFixable = candidateFixability.get(outputCandidateKey(candidate.file, candidate.line, candidate.column)) ?? false;
|
|
41
|
-
if (existing === undefined) {
|
|
42
|
-
grouped.set(adjustedLine, { carriers: [...candidate.carriers], autoFixable });
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
const carriers = existing.carriers;
|
|
46
|
-
for (const carrier of candidate.carriers) {
|
|
47
|
-
if (!carriers.includes(carrier)) {
|
|
48
|
-
carriers.push(carrier);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
existing.autoFixable = existing.autoFixable && autoFixable;
|
|
52
|
-
}
|
|
53
|
-
const candidates = [...grouped.entries()]
|
|
54
|
-
.map(([line, entry]) => ({ line, ...entry }))
|
|
55
|
-
.sort((left, right) => right.line - left.line);
|
|
56
|
-
for (const candidate of candidates) {
|
|
57
|
-
const index = candidate.line - 1;
|
|
58
|
-
if (index < 0 || index > lines.length)
|
|
59
|
-
continue;
|
|
60
|
-
if (index > 0 && isOutputCandidateHintLine(lines[index - 1] ?? ''))
|
|
61
|
-
continue;
|
|
62
|
-
const indentation = lines[index]?.match(/^\s*/)?.[0] ?? '';
|
|
63
|
-
const hint = candidate.autoFixable
|
|
64
|
-
? formatCarrierLineWithExpectOut(indentation, candidate.carriers)
|
|
65
|
-
: formatCarrierLineWithMaybeOut(indentation, candidate.carriers);
|
|
66
|
-
lines.splice(index, 0, hint);
|
|
67
|
-
}
|
|
68
|
-
return lines.join('\n');
|
|
69
|
-
}
|
|
70
|
-
function annotateSourceFile(sourceText, routines, summariesByName) {
|
|
71
|
-
const routineLines = Array.from(routines)
|
|
1
|
+
import { annotateRegisterCareContracts } from './annotate.js';
|
|
2
|
+
import { applyExpectOutFixesToSource, findExpectOutFixesForCandidates, } from './fix.js';
|
|
3
|
+
export function buildAnnotations(loaded, programRoutines, summariesByName, outputCandidates, options) {
|
|
4
|
+
const routines = programRoutines
|
|
72
5
|
.filter((routine) => summariesByName.has(routine.name))
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const summary = summariesByName.get(routine.name);
|
|
81
|
-
if (!summary)
|
|
82
|
-
continue;
|
|
83
|
-
const insertLine = routine.span.start.line - 1;
|
|
84
|
-
if (insertLine < 0 || insertLine > lines.length)
|
|
85
|
-
continue;
|
|
86
|
-
const generatedLines = [
|
|
87
|
-
...(summary.mayRead.length > 0 ? [formatCarrierLine('in', summary.mayRead)] : []),
|
|
88
|
-
...(summary.mayWrite.length > 0 ? [formatCarrierLine('out', summary.mayWrite)] : []),
|
|
89
|
-
...(summary.preserved.length > 0 ? [formatCarrierLine('preserves', summary.preserved)] : []),
|
|
90
|
-
];
|
|
91
|
-
if (generatedLines.length === 0)
|
|
92
|
-
continue;
|
|
93
|
-
let start = insertLine;
|
|
94
|
-
for (let index = insertLine - 1; index >= 0 && isGeneratedRegisterContractLine(lines[index] ?? ''); index -= 1) {
|
|
95
|
-
start = index;
|
|
96
|
-
}
|
|
97
|
-
if (start === insertLine ||
|
|
98
|
-
lines.slice(start, insertLine).some((line) => line.trim().length === 0)) {
|
|
99
|
-
start = insertLine;
|
|
100
|
-
}
|
|
101
|
-
const existing = lines.slice(start, insertLine);
|
|
102
|
-
if (existing.length !== generatedLines.length ||
|
|
103
|
-
existing.some((line, index) => line !== generatedLines[index])) {
|
|
104
|
-
changed = true;
|
|
105
|
-
deltas.push({
|
|
106
|
-
anchorLine: routine.span.start.line,
|
|
107
|
-
delta: generatedLines.length - (insertLine - start),
|
|
108
|
-
});
|
|
109
|
-
lines.splice(start, insertLine - start, ...generatedLines);
|
|
110
|
-
}
|
|
6
|
+
.map((routine) => ({
|
|
7
|
+
routine,
|
|
8
|
+
summary: summariesByName.get(routine.name),
|
|
9
|
+
}));
|
|
10
|
+
const annotated = annotateRegisterCareContracts(loaded.sourceTexts, routines);
|
|
11
|
+
if (!options.fixOutputCandidates) {
|
|
12
|
+
return annotated.map((file) => ({ path: file.path, text: file.text }));
|
|
111
13
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
text:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const byFile = new Map();
|
|
121
|
-
for (const routine of programRoutines) {
|
|
122
|
-
if (!summariesByName.has(routine.name))
|
|
123
|
-
continue;
|
|
124
|
-
const file = byFile.get(routine.span.file);
|
|
125
|
-
if (file === undefined) {
|
|
126
|
-
byFile.set(routine.span.file, [routine]);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
file.push(routine);
|
|
130
|
-
}
|
|
14
|
+
const autoFixableCandidates = outputCandidates.filter((candidate) => options.outputCandidateFixability.get(options.outputCandidateKey(candidate.file, candidate.line, candidate.column)) === true);
|
|
15
|
+
const fixes = findExpectOutFixesForCandidates([...programRoutines], autoFixableCandidates);
|
|
16
|
+
if (fixes.length === 0) {
|
|
17
|
+
return annotated.map((file) => ({ path: file.path, text: file.text }));
|
|
18
|
+
}
|
|
19
|
+
const workingTexts = new Map(loaded.sourceTexts);
|
|
20
|
+
for (const file of annotated) {
|
|
21
|
+
workingTexts.set(file.path, file.text);
|
|
131
22
|
}
|
|
132
23
|
const out = [];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
24
|
+
const fixesByFile = new Map();
|
|
25
|
+
for (const fix of fixes) {
|
|
26
|
+
const items = fixesByFile.get(fix.file) ?? [];
|
|
27
|
+
items.push(fix);
|
|
28
|
+
fixesByFile.set(fix.file, items);
|
|
29
|
+
}
|
|
30
|
+
for (const [path, text] of workingTexts) {
|
|
31
|
+
const reference = loaded.sourceTexts.get(path);
|
|
32
|
+
if (reference === undefined)
|
|
136
33
|
continue;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
text = annotation.text;
|
|
142
|
-
deltas = annotation.deltas;
|
|
143
|
-
}
|
|
144
|
-
if (options.fixOutputCandidates) {
|
|
145
|
-
const candidatesForFile = outputCandidates.filter((candidate) => candidate.file === path);
|
|
146
|
-
if (candidatesForFile.length > 0) {
|
|
147
|
-
text = applyOutputCandidateHints(text, candidatesForFile, options.outputCandidateFixability, deltas, options.outputCandidateKey);
|
|
148
|
-
}
|
|
34
|
+
const fileFixes = fixesByFile.get(path) ?? [];
|
|
35
|
+
const nextText = fileFixes.length > 0 ? applyExpectOutFixesToSource(text, fileFixes, reference) : text;
|
|
36
|
+
if (nextText !== reference) {
|
|
37
|
+
out.push({ path, text: nextText });
|
|
149
38
|
}
|
|
150
|
-
if (text !== sourceText)
|
|
151
|
-
out.push({ path, text });
|
|
152
39
|
}
|
|
153
|
-
return out;
|
|
40
|
+
return out.sort((left, right) => left.path.localeCompare(right.path));
|
|
154
41
|
}
|
|
@@ -6,4 +6,6 @@ export interface RegisterCareExpectOutFix {
|
|
|
6
6
|
routine: string;
|
|
7
7
|
carriers: RegisterCareUnit[];
|
|
8
8
|
}
|
|
9
|
+
export declare function findExpectOutFixesForCandidates(routines: RegisterCareRoutine[], candidates: RegisterCareOutputCandidate[]): RegisterCareExpectOutFix[];
|
|
9
10
|
export declare function autoFixableCandidateKeys(routines: RegisterCareRoutine[], candidates: RegisterCareOutputCandidate[]): Set<string>;
|
|
11
|
+
export declare function applyExpectOutFixesToSource(source: string, fixes: RegisterCareExpectOutFix[], referenceSource?: string): string;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getZ80InstructionEffect } from '../z80/effects.js';
|
|
2
2
|
import { instructionSuccessors, labelIndex } from './controlFlow.js';
|
|
3
|
+
import { contractCarrierList } from './report.js';
|
|
4
|
+
import { joinSourceLines, splitSourceLines } from './sourceText.js';
|
|
3
5
|
function sameLocation(a, b) {
|
|
4
6
|
return a.file === b.file && a.line === b.line && a.column === b.column;
|
|
5
7
|
}
|
|
@@ -68,6 +70,9 @@ function findExpectOutFixes(routines, candidates) {
|
|
|
68
70
|
}
|
|
69
71
|
return out;
|
|
70
72
|
}
|
|
73
|
+
export function findExpectOutFixesForCandidates(routines, candidates) {
|
|
74
|
+
return findExpectOutFixes(routines, candidates);
|
|
75
|
+
}
|
|
71
76
|
export function autoFixableCandidateKeys(routines, candidates) {
|
|
72
77
|
const fixes = findExpectOutFixes(routines, candidates);
|
|
73
78
|
const out = new Set();
|
|
@@ -76,3 +81,50 @@ export function autoFixableCandidateKeys(routines, candidates) {
|
|
|
76
81
|
}
|
|
77
82
|
return out;
|
|
78
83
|
}
|
|
84
|
+
function isExpectOutLine(line) {
|
|
85
|
+
return /^\s*;\s*expects\s+out\b/i.test(line);
|
|
86
|
+
}
|
|
87
|
+
function expectedCallLine(originalLines, fix) {
|
|
88
|
+
return originalLines[fix.line - 1]?.trim();
|
|
89
|
+
}
|
|
90
|
+
function findCallLineIndex(lines, originalLines, fix) {
|
|
91
|
+
const expected = expectedCallLine(originalLines, fix);
|
|
92
|
+
if (!expected)
|
|
93
|
+
return undefined;
|
|
94
|
+
const preferred = fix.line - 1;
|
|
95
|
+
if (lines[preferred]?.trim() === expected)
|
|
96
|
+
return preferred;
|
|
97
|
+
let best;
|
|
98
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
99
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
100
|
+
if (lines[index]?.trim() !== expected)
|
|
101
|
+
continue;
|
|
102
|
+
const distance = Math.abs(index - preferred);
|
|
103
|
+
if (distance < bestDistance) {
|
|
104
|
+
best = index;
|
|
105
|
+
bestDistance = distance;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return best;
|
|
109
|
+
}
|
|
110
|
+
function indentation(line) {
|
|
111
|
+
return line.match(/^\s*/)?.[0] ?? '';
|
|
112
|
+
}
|
|
113
|
+
export function applyExpectOutFixesToSource(source, fixes, referenceSource = source) {
|
|
114
|
+
if (fixes.length === 0)
|
|
115
|
+
return source;
|
|
116
|
+
const originalLines = referenceSource.split(/\r?\n/);
|
|
117
|
+
const sourceLines = splitSourceLines(source);
|
|
118
|
+
const { lines } = sourceLines;
|
|
119
|
+
const sorted = [...fixes].sort((a, b) => b.line - a.line || b.column - a.column);
|
|
120
|
+
for (const fix of sorted) {
|
|
121
|
+
const index = findCallLineIndex(lines, originalLines, fix);
|
|
122
|
+
if (index === undefined)
|
|
123
|
+
continue;
|
|
124
|
+
if (index > 0 && isExpectOutLine(lines[index - 1] ?? ''))
|
|
125
|
+
continue;
|
|
126
|
+
const prefix = indentation(lines[index] ?? '');
|
|
127
|
+
lines.splice(index, 0, `${prefix}; expects out ${contractCarrierList(fix.carriers)}`);
|
|
128
|
+
}
|
|
129
|
+
return joinSourceLines(sourceLines);
|
|
130
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Z80Instruction, Z80Operand } from '../z80/instruction.js';
|
|
2
|
+
import type { RegisterCareInstruction } from './types.js';
|
|
3
|
+
export declare function instructionHead(item: RegisterCareInstruction): string;
|
|
4
|
+
export declare function regName(operand: Z80Operand | undefined): string | undefined;
|
|
5
|
+
export declare function instructionOperandCount(instruction: Z80Instruction): number;
|
|
6
|
+
export declare function instructionOperand(instruction: Z80Instruction, index: number): Z80Operand | undefined;
|
|
7
|
+
export declare function isUnconditionalReturnInstruction(item: RegisterCareInstruction): boolean;
|
|
8
|
+
export declare function isPureTokenTransferInstruction(item: RegisterCareInstruction): boolean;
|
|
9
|
+
export declare function isAccumulatorSelfOperand(item: RegisterCareInstruction): boolean;
|
|
10
|
+
export declare function isImmediateZeroOperand(item: RegisterCareInstruction): boolean;
|
|
11
|
+
export declare function isRegisterOperand(item: RegisterCareInstruction | undefined, index: number, name: string): boolean;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export function instructionHead(item) {
|
|
2
|
+
return item.instruction.mnemonic.toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
export function regName(operand) {
|
|
5
|
+
if (operand === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
switch (operand.kind) {
|
|
8
|
+
case 'reg8':
|
|
9
|
+
return operand.register.toUpperCase();
|
|
10
|
+
case 'reg16':
|
|
11
|
+
return operand.register.toUpperCase();
|
|
12
|
+
case 'reg-index16':
|
|
13
|
+
return operand.register.toUpperCase();
|
|
14
|
+
case 'reg-half-index':
|
|
15
|
+
return operand.register.toUpperCase();
|
|
16
|
+
default:
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function immValue(operand) {
|
|
21
|
+
if (operand?.kind !== 'imm')
|
|
22
|
+
return undefined;
|
|
23
|
+
const expression = operand.expression;
|
|
24
|
+
return expression.kind === 'number' ? expression.value : undefined;
|
|
25
|
+
}
|
|
26
|
+
export function instructionOperandCount(instruction) {
|
|
27
|
+
switch (instruction.mnemonic) {
|
|
28
|
+
case 'ret':
|
|
29
|
+
case 'ret-cc':
|
|
30
|
+
return instruction.mnemonic === 'ret' ? 0 : 1;
|
|
31
|
+
case 'ld':
|
|
32
|
+
return 2;
|
|
33
|
+
case 'ex':
|
|
34
|
+
return 2;
|
|
35
|
+
case 'jp':
|
|
36
|
+
case 'jp-cc':
|
|
37
|
+
case 'jr':
|
|
38
|
+
case 'jr-cc':
|
|
39
|
+
case 'djnz':
|
|
40
|
+
case 'call':
|
|
41
|
+
case 'call-cc':
|
|
42
|
+
return 1;
|
|
43
|
+
case 'add':
|
|
44
|
+
case 'adc':
|
|
45
|
+
case 'sbc':
|
|
46
|
+
return 'target' in instruction ? 2 : 1;
|
|
47
|
+
case 'sub':
|
|
48
|
+
case 'and':
|
|
49
|
+
case 'or':
|
|
50
|
+
case 'xor':
|
|
51
|
+
case 'cp':
|
|
52
|
+
return 1;
|
|
53
|
+
default:
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function instructionOperand(instruction, index) {
|
|
58
|
+
switch (instruction.mnemonic) {
|
|
59
|
+
case 'ld':
|
|
60
|
+
return index === 0 ? instruction.target : index === 1 ? instruction.source : undefined;
|
|
61
|
+
case 'ex': {
|
|
62
|
+
if (index === 0) {
|
|
63
|
+
return instruction.form === 'de-hl'
|
|
64
|
+
? { kind: 'reg16', register: 'de' }
|
|
65
|
+
: instruction.form === 'af-af'
|
|
66
|
+
? { kind: 'reg16', register: 'af' }
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
if (index === 1) {
|
|
70
|
+
return instruction.form === 'de-hl'
|
|
71
|
+
? { kind: 'reg16', register: 'hl' }
|
|
72
|
+
: undefined;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
case 'add':
|
|
77
|
+
case 'adc':
|
|
78
|
+
case 'sbc':
|
|
79
|
+
if ('target' in instruction) {
|
|
80
|
+
return index === 0 ? instruction.target : index === 1 ? instruction.source : undefined;
|
|
81
|
+
}
|
|
82
|
+
return index === 0 ? instruction.source : undefined;
|
|
83
|
+
case 'sub':
|
|
84
|
+
case 'and':
|
|
85
|
+
case 'or':
|
|
86
|
+
case 'xor':
|
|
87
|
+
case 'cp':
|
|
88
|
+
return index === 0 ? instruction.source : undefined;
|
|
89
|
+
default:
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function isUnconditionalReturnInstruction(item) {
|
|
94
|
+
const head = instructionHead(item);
|
|
95
|
+
if (head === 'ret')
|
|
96
|
+
return item.instruction.mnemonic === 'ret';
|
|
97
|
+
return head === 'retn' || head === 'reti';
|
|
98
|
+
}
|
|
99
|
+
export function isPureTokenTransferInstruction(item) {
|
|
100
|
+
const head = instructionHead(item);
|
|
101
|
+
if (head === 'ex')
|
|
102
|
+
return true;
|
|
103
|
+
if (head !== 'ld' || instructionOperandCount(item.instruction) !== 2)
|
|
104
|
+
return false;
|
|
105
|
+
const dst = instructionOperand(item.instruction, 0);
|
|
106
|
+
const src = instructionOperand(item.instruction, 1);
|
|
107
|
+
if (regName(dst) === undefined)
|
|
108
|
+
return false;
|
|
109
|
+
return regName(src) !== undefined || src?.kind === 'imm';
|
|
110
|
+
}
|
|
111
|
+
export function isAccumulatorSelfOperand(item) {
|
|
112
|
+
const inst = item.instruction;
|
|
113
|
+
if (inst.mnemonic === 'or' || inst.mnemonic === 'and' || inst.mnemonic === 'xor') {
|
|
114
|
+
return inst.source.kind === 'reg8' && inst.source.register === 'a';
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
export function isImmediateZeroOperand(item) {
|
|
119
|
+
const inst = item.instruction;
|
|
120
|
+
if (inst.mnemonic !== 'cp')
|
|
121
|
+
return false;
|
|
122
|
+
return immValue(inst.source) === 0;
|
|
123
|
+
}
|
|
124
|
+
export function isRegisterOperand(item, index, name) {
|
|
125
|
+
if (item === undefined)
|
|
126
|
+
return false;
|
|
127
|
+
const operand = instructionOperand(item.instruction, index);
|
|
128
|
+
return regName(operand) === name.toUpperCase();
|
|
129
|
+
}
|
|
@@ -48,10 +48,7 @@ function hintUnitsForLine(hints, file, callLine) {
|
|
|
48
48
|
return prior?.comment.kind === 'expectOut' ? unique(prior.comment.carriers) : [];
|
|
49
49
|
}
|
|
50
50
|
function outputUnits(summary) {
|
|
51
|
-
return unique(
|
|
52
|
-
...(summary.valueRelations?.flatMap((relation) => relation.out) ?? []),
|
|
53
|
-
...(summary.mayOutput ?? []),
|
|
54
|
-
]);
|
|
51
|
+
return unique(summary.valueRelations.flatMap((relation) => relation.out));
|
|
55
52
|
}
|
|
56
53
|
function setEqual(left, right) {
|
|
57
54
|
if (left.size !== right.size)
|
|
@@ -148,7 +145,7 @@ export function findRegisterCareConflicts(routine, summaries, hints) {
|
|
|
148
145
|
for (const unit of outputUnits(summary))
|
|
149
146
|
accepted.add(unit);
|
|
150
147
|
const carriers = unique(summary.mayWrite.filter((unit) => liveOut[index].has(unit) && !accepted.has(unit)));
|
|
151
|
-
if (carriers.length > 0
|
|
148
|
+
if (carriers.length > 0) {
|
|
152
149
|
conflicts.push({
|
|
153
150
|
file: item.file,
|
|
154
151
|
line: item.line,
|
|
@@ -171,8 +168,19 @@ export function findCallerOutputCandidateObservations(routines, summaries) {
|
|
|
171
168
|
for (const routine of routines) {
|
|
172
169
|
const { liveOut } = liveSetsForRoutine(routine, summaries);
|
|
173
170
|
for (const { item, index, boundary, target, summary } of resolvedBoundariesForRoutine(routine, summaries)) {
|
|
174
|
-
const
|
|
175
|
-
const
|
|
171
|
+
const intentionalOutputs = new Set(outputUnits(summary));
|
|
172
|
+
const carrierSources = [];
|
|
173
|
+
for (const unit of summary.mayWrite) {
|
|
174
|
+
if (liveOut[index].has(unit) && !intentionalOutputs.has(unit)) {
|
|
175
|
+
carrierSources.push(unit);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const unit of intentionalOutputs) {
|
|
179
|
+
if (liveOut[index].has(unit)) {
|
|
180
|
+
carrierSources.push(unit);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const carriers = unique(carrierSources);
|
|
176
184
|
if (carriers.length > 0) {
|
|
177
185
|
out.push({
|
|
178
186
|
file: item.file,
|
|
@@ -23,6 +23,8 @@ export function getRegisterCareProfile(name) {
|
|
|
23
23
|
mayOutput: [],
|
|
24
24
|
preserved: ['B', 'C', 'D', 'E', 'H', 'L'],
|
|
25
25
|
valueRelations: [],
|
|
26
|
+
stackBalanced: true,
|
|
27
|
+
hasUnknownStackEffect: false,
|
|
26
28
|
},
|
|
27
29
|
],
|
|
28
30
|
]),
|
|
@@ -36,6 +38,8 @@ export function getRegisterCareProfile(name) {
|
|
|
36
38
|
mayOutput: ['A', 'carry', 'zero'],
|
|
37
39
|
preserved: ['B', 'C', 'D', 'E', 'H', 'L'],
|
|
38
40
|
valueRelations: [{ out: ['A', 'carry', 'zero'], from: [] }],
|
|
41
|
+
stackBalanced: true,
|
|
42
|
+
hasUnknownStackEffect: false,
|
|
39
43
|
},
|
|
40
44
|
],
|
|
41
45
|
]),
|
|
@@ -13,6 +13,21 @@ function instructionCallTarget(item) {
|
|
|
13
13
|
}
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
16
|
+
function instructionTailJumpTarget(item, entryNames) {
|
|
17
|
+
if (item.kind !== 'instruction')
|
|
18
|
+
return undefined;
|
|
19
|
+
const mnemonic = item.instruction.mnemonic;
|
|
20
|
+
if (mnemonic === 'jp-cc' && entryNames === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (mnemonic !== 'jp' && mnemonic !== 'jp-cc')
|
|
23
|
+
return undefined;
|
|
24
|
+
const target = routineNameFromExpression(item.instruction.expression);
|
|
25
|
+
if (target === undefined || target.startsWith('.'))
|
|
26
|
+
return undefined;
|
|
27
|
+
if (entryNames !== undefined && !entryNames.has(target))
|
|
28
|
+
return undefined;
|
|
29
|
+
return target;
|
|
30
|
+
}
|
|
16
31
|
function toInstruction(item, labels) {
|
|
17
32
|
return {
|
|
18
33
|
instruction: item.instruction,
|
|
@@ -22,9 +37,16 @@ function toInstruction(item, labels) {
|
|
|
22
37
|
labels: [...labels],
|
|
23
38
|
};
|
|
24
39
|
}
|
|
40
|
+
function pushDirectBoundary(boundaries, target, subject, file, line, column) {
|
|
41
|
+
boundaries.push({ target, subject, file, line, column });
|
|
42
|
+
}
|
|
25
43
|
export function buildRegisterCareProgramModel(items) {
|
|
26
44
|
const routines = [];
|
|
27
45
|
const directCalls = [];
|
|
46
|
+
const filesWithEntryLabels = new Set(items
|
|
47
|
+
.filter((item) => item.kind === 'label')
|
|
48
|
+
.filter((item) => item.isEntry === true)
|
|
49
|
+
.map((item) => item.span.sourceName));
|
|
28
50
|
let routineName;
|
|
29
51
|
let entryLabels = [];
|
|
30
52
|
let labels = [];
|
|
@@ -35,14 +57,28 @@ export function buildRegisterCareProgramModel(items) {
|
|
|
35
57
|
const startRoutine = (item) => {
|
|
36
58
|
sourceName = item.span.sourceName;
|
|
37
59
|
routineName = item.name;
|
|
38
|
-
entryLabels = [item.name];
|
|
60
|
+
entryLabels = item.isEntry === true ? [item.name] : [];
|
|
39
61
|
labels = [item.name];
|
|
40
62
|
routineStartLine = item.span.line;
|
|
41
63
|
routineStartColumn = item.span.column;
|
|
42
64
|
instructions = [];
|
|
43
65
|
};
|
|
44
66
|
const flushRoutine = () => {
|
|
45
|
-
if (routineName === undefined ||
|
|
67
|
+
if (routineName === undefined || routineStartLine === undefined) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (instructions.length === 0) {
|
|
71
|
+
routines.push({
|
|
72
|
+
name: routineName,
|
|
73
|
+
labels: [...labels],
|
|
74
|
+
entryLabels: [...entryLabels],
|
|
75
|
+
instructions: [],
|
|
76
|
+
span: {
|
|
77
|
+
file: sourceName ?? '',
|
|
78
|
+
start: { line: routineStartLine, column: routineStartColumn ?? 1 },
|
|
79
|
+
end: { line: routineStartLine, column: routineStartColumn ?? 1 },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
46
82
|
return;
|
|
47
83
|
}
|
|
48
84
|
const end = instructions[instructions.length - 1];
|
|
@@ -88,12 +124,7 @@ export function buildRegisterCareProgramModel(items) {
|
|
|
88
124
|
instructions.push(toInstruction(item, labels));
|
|
89
125
|
const directTarget = instructionCallTarget(item);
|
|
90
126
|
if (directTarget !== undefined) {
|
|
91
|
-
directCalls.
|
|
92
|
-
target: directTarget,
|
|
93
|
-
file: item.span.sourceName,
|
|
94
|
-
line: item.span.line,
|
|
95
|
-
column: item.span.column,
|
|
96
|
-
});
|
|
127
|
+
pushDirectBoundary(directCalls, directTarget, `CALL ${directTarget}`, item.span.sourceName, item.span.line, item.span.column);
|
|
97
128
|
}
|
|
98
129
|
continue;
|
|
99
130
|
}
|
|
@@ -104,19 +135,54 @@ export function buildRegisterCareProgramModel(items) {
|
|
|
104
135
|
continue;
|
|
105
136
|
}
|
|
106
137
|
if (routineName === undefined) {
|
|
138
|
+
if (filesWithEntryLabels.has(item.span.sourceName) && item.isEntry !== true) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
107
141
|
startRoutine(item);
|
|
108
142
|
continue;
|
|
109
143
|
}
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
144
|
+
if (sourceName === undefined || sourceName !== item.span.sourceName) {
|
|
145
|
+
finalizeAndRestart(item);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (instructions.length > 0) {
|
|
149
|
+
if (filesWithEntryLabels.has(item.span.sourceName) && item.isEntry !== true) {
|
|
150
|
+
labels.push(item.name);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
113
153
|
finalizeAndRestart(item);
|
|
114
154
|
continue;
|
|
115
155
|
}
|
|
116
156
|
// Multiple global labels before body on same routine are coalesced as entry labels.
|
|
117
157
|
labels.push(item.name);
|
|
118
|
-
|
|
158
|
+
if (item.isEntry === true) {
|
|
159
|
+
entryLabels.push(item.name);
|
|
160
|
+
}
|
|
119
161
|
}
|
|
120
162
|
flushRoutine();
|
|
121
|
-
|
|
163
|
+
const entryNamesByFile = new Map();
|
|
164
|
+
for (const item of items) {
|
|
165
|
+
if (item.kind !== 'label' || item.isEntry !== true)
|
|
166
|
+
continue;
|
|
167
|
+
const names = entryNamesByFile.get(item.span.sourceName) ?? new Set();
|
|
168
|
+
names.add(item.name);
|
|
169
|
+
entryNamesByFile.set(item.span.sourceName, names);
|
|
170
|
+
}
|
|
171
|
+
const directTailJumps = [];
|
|
172
|
+
for (const item of items) {
|
|
173
|
+
if (item.kind !== 'instruction')
|
|
174
|
+
continue;
|
|
175
|
+
const entryNames = filesWithEntryLabels.has(item.span.sourceName)
|
|
176
|
+
? entryNamesByFile.get(item.span.sourceName)
|
|
177
|
+
: undefined;
|
|
178
|
+
const target = instructionTailJumpTarget(item, entryNames);
|
|
179
|
+
if (target === undefined)
|
|
180
|
+
continue;
|
|
181
|
+
pushDirectBoundary(directTailJumps, target, `JP ${target}`, item.span.sourceName, item.span.line, item.span.column);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
routines,
|
|
185
|
+
directCalls,
|
|
186
|
+
directBoundaries: [...directCalls, ...directTailJumps],
|
|
187
|
+
};
|
|
122
188
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RegisterCareReportModel, RegisterCareUnit, RoutineSummary } from './types.js';
|
|
2
|
-
export declare function renderRegisterCareReport(model: RegisterCareReportModel): string;
|
|
3
2
|
export declare function contractCarrierList(units: RegisterCareUnit[]): string;
|
|
3
|
+
export declare function renderRegisterCareReport(model: RegisterCareReportModel): string;
|
|
4
4
|
export declare function renderRegisterCareInterface(summaries: RoutineSummary[]): string;
|
|
5
|
+
export declare function renderRegisterCareSourceBlock(summary: RoutineSummary): string[];
|