@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,23 +1,94 @@
|
|
|
1
|
-
function
|
|
1
|
+
function list(units) {
|
|
2
2
|
return units.length === 0 ? '-' : units.join(',');
|
|
3
3
|
}
|
|
4
|
+
const FLAG_UNITS = new Set(['carry', 'zero', 'sign', 'parity', 'halfCarry']);
|
|
5
|
+
const CONTRACT_CARRIER_PAIRS = [
|
|
6
|
+
{ label: 'BC', hi: 'B', lo: 'C' },
|
|
7
|
+
{ label: 'DE', hi: 'D', lo: 'E' },
|
|
8
|
+
{ label: 'HL', hi: 'H', lo: 'L' },
|
|
9
|
+
{ label: 'IX', hi: 'IXH', lo: 'IXL' },
|
|
10
|
+
{ label: 'IY', hi: 'IYH', lo: 'IYL' },
|
|
11
|
+
{ label: 'SP', hi: 'SPH', lo: 'SPL' },
|
|
12
|
+
];
|
|
13
|
+
export function contractCarrierList(units) {
|
|
14
|
+
const unique = [...new Set(units)];
|
|
15
|
+
const unitSet = new Set(unique);
|
|
16
|
+
const emitted = new Set();
|
|
17
|
+
const parts = [];
|
|
18
|
+
for (const unit of unique) {
|
|
19
|
+
if (emitted.has(unit))
|
|
20
|
+
continue;
|
|
21
|
+
const pair = CONTRACT_CARRIER_PAIRS.find((candidate) => (candidate.hi === unit || candidate.lo === unit) &&
|
|
22
|
+
unitSet.has(candidate.hi) &&
|
|
23
|
+
unitSet.has(candidate.lo));
|
|
24
|
+
if (pair) {
|
|
25
|
+
parts.push(pair.label);
|
|
26
|
+
emitted.add(pair.hi);
|
|
27
|
+
emitted.add(pair.lo);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
parts.push(unit);
|
|
31
|
+
emitted.add(unit);
|
|
32
|
+
}
|
|
33
|
+
return parts.length === 0 ? '-' : parts.join(',');
|
|
34
|
+
}
|
|
35
|
+
function relationOutputUnits(relations) {
|
|
36
|
+
return relations.flatMap((rel) => rel.out);
|
|
37
|
+
}
|
|
38
|
+
function contractEntries(summary) {
|
|
39
|
+
const out = [];
|
|
40
|
+
if (summary.mayRead.length > 0)
|
|
41
|
+
out.push({ keyword: 'in', carriers: contractCarrierList(summary.mayRead) });
|
|
42
|
+
const outputUnits = relationOutputUnits(summary.valueRelations);
|
|
43
|
+
if (outputUnits.length > 0)
|
|
44
|
+
out.push({ keyword: 'out', carriers: contractCarrierList(outputUnits) });
|
|
45
|
+
const relationOut = relationOutUnits(summary);
|
|
46
|
+
const clobbers = summary.mayWrite.filter((unit) => !relationOut.has(unit));
|
|
47
|
+
if (clobbers.length > 0)
|
|
48
|
+
out.push({ keyword: 'clobbers', carriers: contractCarrierList(clobbers) });
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function sourceContractEntries(summary) {
|
|
52
|
+
const out = [];
|
|
53
|
+
if (summary.mayRead.length > 0)
|
|
54
|
+
out.push({ keyword: 'in', carriers: contractCarrierList(summary.mayRead) });
|
|
55
|
+
const relationOut = relationOutUnits(summary);
|
|
56
|
+
const candidates = (summary.outputCandidates ?? []).filter((unit) => !relationOut.has(unit));
|
|
57
|
+
if (candidates.length > 0)
|
|
58
|
+
out.push({ keyword: 'maybe-out', carriers: contractCarrierList(candidates) });
|
|
59
|
+
const outputUnits = relationOutputUnits(summary.valueRelations);
|
|
60
|
+
if (outputUnits.length > 0)
|
|
61
|
+
out.push({ keyword: 'out', carriers: contractCarrierList(outputUnits) });
|
|
62
|
+
const clobbers = summary.mayWrite.filter((unit) => !relationOut.has(unit) && !FLAG_UNITS.has(unit));
|
|
63
|
+
if (clobbers.length > 0)
|
|
64
|
+
out.push({ keyword: 'clobbers', carriers: contractCarrierList(clobbers) });
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
function stackStatus(summary) {
|
|
68
|
+
const balance = summary.stackBalanced ? 'balanced' : 'unbalanced';
|
|
69
|
+
return summary.hasUnknownStackEffect ? `${balance}, unknown effect` : balance;
|
|
70
|
+
}
|
|
71
|
+
function relationOutUnits(summary) {
|
|
72
|
+
return new Set(summary.valueRelations.flatMap((rel) => rel.out));
|
|
73
|
+
}
|
|
4
74
|
export function renderRegisterCareReport(model) {
|
|
5
|
-
const lines = [
|
|
6
|
-
|
|
7
|
-
`
|
|
8
|
-
|
|
9
|
-
...(model.profile !== undefined ? [`Profile: ${model.profile}`] : []),
|
|
10
|
-
'',
|
|
11
|
-
];
|
|
75
|
+
const lines = ['AZM Register-Care Report', `Entry: ${model.entryFile}`, `Mode: ${model.mode}`];
|
|
76
|
+
if (model.profile)
|
|
77
|
+
lines.push(`Profile: ${model.profile}`);
|
|
78
|
+
lines.push('');
|
|
12
79
|
if (model.summaries.length === 0) {
|
|
13
80
|
lines.push('Routines: none', '');
|
|
14
81
|
}
|
|
15
82
|
else {
|
|
16
83
|
for (const summary of model.summaries) {
|
|
17
84
|
lines.push(`Routine: ${summary.name}`);
|
|
18
|
-
lines.push(` reads: ${
|
|
19
|
-
lines.push(` writes: ${
|
|
20
|
-
lines.push(` preserves: ${
|
|
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
|
+
}
|
|
21
92
|
lines.push('');
|
|
22
93
|
}
|
|
23
94
|
}
|
|
@@ -27,17 +98,17 @@ export function renderRegisterCareReport(model) {
|
|
|
27
98
|
}
|
|
28
99
|
else {
|
|
29
100
|
for (const conflict of model.conflicts) {
|
|
30
|
-
lines.push(` ${conflict.file}:${conflict.line}:${conflict.column}: ${conflict.callTarget}: ${conflict.message}`);
|
|
101
|
+
lines.push(` ${conflict.file}:${conflict.line}:${conflict.column}: ${conflict.callTarget}: ${list(conflict.carriers)}: ${conflict.message}`);
|
|
31
102
|
}
|
|
32
103
|
}
|
|
33
104
|
lines.push('');
|
|
34
105
|
lines.push('Output candidates:');
|
|
35
|
-
if (model.outputCandidates
|
|
106
|
+
if (!model.outputCandidates || model.outputCandidates.length === 0) {
|
|
36
107
|
lines.push(' none');
|
|
37
108
|
}
|
|
38
109
|
else {
|
|
39
110
|
for (const candidate of model.outputCandidates) {
|
|
40
|
-
lines.push(` ${candidate.file}:${candidate.line}:${candidate.column}: ${candidate.routine}: ${candidate.carriers
|
|
111
|
+
lines.push(` ${candidate.file}:${candidate.line}:${candidate.column}: ${candidate.routine}: ${list(candidate.carriers)}: ${candidate.message}`);
|
|
41
112
|
}
|
|
42
113
|
}
|
|
43
114
|
lines.push('');
|
|
@@ -46,37 +117,23 @@ export function renderRegisterCareReport(model) {
|
|
|
46
117
|
lines.push(' none');
|
|
47
118
|
}
|
|
48
119
|
else {
|
|
49
|
-
for (const call of model.unknownCalls)
|
|
120
|
+
for (const call of model.unknownCalls)
|
|
50
121
|
lines.push(` ${call}`);
|
|
51
|
-
}
|
|
52
122
|
}
|
|
53
123
|
lines.push('');
|
|
54
124
|
return `${lines.join('\n')}\n`;
|
|
55
125
|
}
|
|
56
|
-
export function contractCarrierList(units) {
|
|
57
|
-
return units.length === 0 ? '-' : units.join(',');
|
|
58
|
-
}
|
|
59
126
|
export function renderRegisterCareInterface(summaries) {
|
|
60
127
|
const lines = [];
|
|
61
128
|
for (const summary of summaries) {
|
|
62
|
-
if (summary.mayRead.length === 0 &&
|
|
63
|
-
summary.mayWrite.length === 0 &&
|
|
64
|
-
summary.preserved.length === 0) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
129
|
lines.push(`extern ${summary.name}`);
|
|
68
|
-
|
|
69
|
-
lines.push(
|
|
70
|
-
}
|
|
71
|
-
if (summary.mayWrite.length > 0) {
|
|
72
|
-
lines.push(`clobbers ${contractCarrierList(summary.mayWrite)}`);
|
|
73
|
-
}
|
|
74
|
-
if (summary.preserved.length > 0) {
|
|
75
|
-
lines.push(`preserves ${contractCarrierList(summary.preserved)}`);
|
|
130
|
+
for (const entry of contractEntries(summary)) {
|
|
131
|
+
lines.push(`${entry.keyword} ${entry.carriers}`);
|
|
76
132
|
}
|
|
77
133
|
lines.push('end', '');
|
|
78
134
|
}
|
|
79
|
-
if (lines.length === 0)
|
|
80
|
-
lines.push('No inferred contracts were emitted.', '');
|
|
81
135
|
return `${lines.join('\n')}\n`;
|
|
82
136
|
}
|
|
137
|
+
export function renderRegisterCareSourceBlock(summary) {
|
|
138
|
+
return sourceContractEntries(summary).map((entry) => `;! ${entry.keyword.padEnd(10)}${entry.carriers}`);
|
|
139
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RegisterCareRoutine, 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: RegisterCareRoutine[], contracts: Map<string, RoutineContract>, routineNameSet: Set<string>, profileSummaries: RoutineSummary[]): Array<{
|
|
4
|
+
routine: RegisterCareRoutine;
|
|
5
|
+
summary: RoutineSummary;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { applyRoutineContract, inferRoutineSummary } from './summary.js';
|
|
2
|
+
function emptyRoutineSummary(name) {
|
|
3
|
+
return {
|
|
4
|
+
name,
|
|
5
|
+
mayRead: [],
|
|
6
|
+
mayWrite: [],
|
|
7
|
+
preserved: [],
|
|
8
|
+
valueRelations: [],
|
|
9
|
+
stackBalanced: true,
|
|
10
|
+
hasUnknownStackEffect: false,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function isLocalLabel(name) {
|
|
14
|
+
return name.startsWith('.');
|
|
15
|
+
}
|
|
16
|
+
function nonLocalLabels(labels) {
|
|
17
|
+
return labels.filter((label) => !isLocalLabel(label));
|
|
18
|
+
}
|
|
19
|
+
function boundaryLabels(routine) {
|
|
20
|
+
return routine.entryLabels.length > 0 ? routine.entryLabels : nonLocalLabels(routine.labels);
|
|
21
|
+
}
|
|
22
|
+
function contractForRoutine(routine, contracts) {
|
|
23
|
+
return boundaryLabels(routine)
|
|
24
|
+
.map((label) => contracts.get(label))
|
|
25
|
+
.find((contract) => contract !== undefined);
|
|
26
|
+
}
|
|
27
|
+
export function summariesWithExternalContracts(summaries, contracts, routineNameSet) {
|
|
28
|
+
const out = [...summaries];
|
|
29
|
+
for (const contract of contracts.values()) {
|
|
30
|
+
if (!routineNameSet.has(contract.name)) {
|
|
31
|
+
out.push(applyRoutineContract(emptyRoutineSummary(contract.name), contract));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function buildBoundarySummaryMap(summaries, routineSummaries, profileSummaries) {
|
|
37
|
+
const boundarySummaryMap = new Map(profileSummaries.map((summary) => [summary.name, summary]));
|
|
38
|
+
for (const summary of summaries) {
|
|
39
|
+
boundarySummaryMap.set(summary.name, summary);
|
|
40
|
+
}
|
|
41
|
+
for (const { routine, summary } of routineSummaries) {
|
|
42
|
+
for (const label of boundaryLabels(routine)) {
|
|
43
|
+
boundarySummaryMap.set(label, summary);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return boundarySummaryMap;
|
|
47
|
+
}
|
|
48
|
+
function summarizeRoutines(routines, contracts, boundarySummaryMap = new Map()) {
|
|
49
|
+
return routines.map((routine) => {
|
|
50
|
+
const inferred = inferRoutineSummary(routine, boundarySummaryMap);
|
|
51
|
+
const contract = contractForRoutine(routine, contracts);
|
|
52
|
+
return { routine, summary: contract ? applyRoutineContract(inferred, contract) : inferred };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function sortedUnique(values) {
|
|
56
|
+
return Array.from(new Set(values)).sort();
|
|
57
|
+
}
|
|
58
|
+
function summaryFingerprint(summary) {
|
|
59
|
+
const relations = summary.valueRelations
|
|
60
|
+
.map((relation) => `${relation.out.join(',')}<-${relation.from.join(',')}`)
|
|
61
|
+
.sort();
|
|
62
|
+
return JSON.stringify({
|
|
63
|
+
name: summary.name,
|
|
64
|
+
mayRead: sortedUnique(summary.mayRead),
|
|
65
|
+
mayWrite: sortedUnique(summary.mayWrite),
|
|
66
|
+
preserved: sortedUnique(summary.preserved),
|
|
67
|
+
relations,
|
|
68
|
+
stackBalanced: summary.stackBalanced,
|
|
69
|
+
hasUnknownStackEffect: summary.hasUnknownStackEffect,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function routineSummariesFingerprint(routineSummaries) {
|
|
73
|
+
return routineSummaries.map((item) => summaryFingerprint(item.summary)).join('\n');
|
|
74
|
+
}
|
|
75
|
+
export function inferRoutineSummariesToFixedPoint(routines, contracts, routineNameSet, profileSummaries) {
|
|
76
|
+
let routineSummaries = summarizeRoutines(routines, contracts);
|
|
77
|
+
const maxPasses = Math.max(2, routines.length + 2);
|
|
78
|
+
for (let pass = 0; pass < maxPasses; pass += 1) {
|
|
79
|
+
const summaries = summariesWithExternalContracts(routineSummaries.map((item) => item.summary), contracts, routineNameSet);
|
|
80
|
+
const boundarySummaryMap = buildBoundarySummaryMap(summaries, routineSummaries, profileSummaries);
|
|
81
|
+
const nextRoutineSummaries = summarizeRoutines(routines, contracts, boundarySummaryMap);
|
|
82
|
+
if (routineSummariesFingerprint(nextRoutineSummaries) ===
|
|
83
|
+
routineSummariesFingerprint(routineSummaries)) {
|
|
84
|
+
return nextRoutineSummaries;
|
|
85
|
+
}
|
|
86
|
+
routineSummaries = nextRoutineSummaries;
|
|
87
|
+
}
|
|
88
|
+
return routineSummaries;
|
|
89
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type SourceLines = {
|
|
2
|
+
lines: string[];
|
|
3
|
+
trailingNewline: boolean;
|
|
4
|
+
eol: '\n' | '\r\n';
|
|
5
|
+
};
|
|
6
|
+
export declare function splitSourceLines(text: string): SourceLines;
|
|
7
|
+
export declare function joinSourceLines({ lines, trailingNewline, eol }: SourceLines): string;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function lineEnding(text) {
|
|
2
|
+
return text.includes('\r\n') ? '\r\n' : '\n';
|
|
3
|
+
}
|
|
4
|
+
export function splitSourceLines(text) {
|
|
5
|
+
const eol = lineEnding(text);
|
|
6
|
+
const trailingNewline = text.endsWith('\n');
|
|
7
|
+
const lines = text.split(/\r?\n/);
|
|
8
|
+
if (trailingNewline)
|
|
9
|
+
lines.pop();
|
|
10
|
+
return { lines, trailingNewline, eol };
|
|
11
|
+
}
|
|
12
|
+
export function joinSourceLines({ lines, trailingNewline, eol }) {
|
|
13
|
+
const text = lines.join(eol);
|
|
14
|
+
return trailingNewline ? `${text}${eol}` : text;
|
|
15
|
+
}
|
|
@@ -3,10 +3,10 @@ import type { AnalyzeRegisterCareOptions, RegisterCareDirectCall, RegisterCareRo
|
|
|
3
3
|
export declare function buildProfileSummaries(profileName: AnalyzeRegisterCareOptions['registerCareProfile']): RoutineSummary[];
|
|
4
4
|
export declare function buildProfileSummaryLookup(profileName: AnalyzeRegisterCareOptions['registerCareProfile']): Map<string, RoutineSummary>;
|
|
5
5
|
export declare function routineNames(routines: readonly RegisterCareRoutine[]): string[];
|
|
6
|
-
export declare function buildSummaries(routines: readonly RegisterCareRoutine[], contractMap: Map<string, RoutineContract
|
|
6
|
+
export declare function buildSummaries(routines: readonly RegisterCareRoutine[], contractMap: Map<string, RoutineContract>, profileSummaries?: readonly RoutineSummary[]): RoutineSummary[];
|
|
7
7
|
export declare function buildSummaryByName(routines: readonly RegisterCareRoutine[], summaries: readonly RoutineSummary[], profileSummaries?: readonly RoutineSummary[]): Map<string, RoutineSummary>;
|
|
8
8
|
export declare function withAcceptedOutputs(summaries: readonly RoutineSummary[], acceptedOutputCandidates: ReadonlyMap<string, RegisterCareUnit[]> | undefined): RoutineSummary[];
|
|
9
|
-
export declare function unknownBoundaryDiagnostics(
|
|
10
|
-
export declare function unknownCallList(
|
|
9
|
+
export declare function unknownBoundaryDiagnostics(directBoundaries: readonly RegisterCareDirectCall[], knownRoutines: ReadonlySet<string>): Diagnostic[];
|
|
10
|
+
export declare function unknownCallList(directBoundaries: readonly RegisterCareDirectCall[], knownRoutines: ReadonlySet<string>): string[];
|
|
11
11
|
export declare function buildOutputCandidateFixability(routines: readonly RegisterCareRoutine[], outputCandidates: readonly RegisterCareOutputCandidate[], autoFixableCandidateKeys: (routines: RegisterCareRoutine[], outputCandidates: RegisterCareOutputCandidate[]) => ReadonlySet<string>): ReadonlyMap<string, boolean>;
|
|
12
12
|
export declare function outputCandidateKey(file: string, line: number, column: number): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getRegisterCareProfile } from './profiles.js';
|
|
2
|
-
import {
|
|
2
|
+
import { inferRoutineSummariesToFixedPoint, summariesWithExternalContracts, } from './routine-summaries.js';
|
|
3
3
|
function unique(values) {
|
|
4
4
|
const out = [];
|
|
5
5
|
for (const value of values) {
|
|
@@ -8,22 +8,16 @@ function unique(values) {
|
|
|
8
8
|
}
|
|
9
9
|
return out;
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
name: routine.name,
|
|
23
|
-
mayRead: Array.from(reads),
|
|
24
|
-
mayWrite: Array.from(writes),
|
|
25
|
-
preserved: [],
|
|
26
|
-
};
|
|
11
|
+
function isLocalLabel(name) {
|
|
12
|
+
return name.startsWith('.');
|
|
13
|
+
}
|
|
14
|
+
function boundaryLabels(routine) {
|
|
15
|
+
return routine.entryLabels.length > 0
|
|
16
|
+
? routine.entryLabels
|
|
17
|
+
: routine.labels.filter((label) => !isLocalLabel(label));
|
|
18
|
+
}
|
|
19
|
+
function routineNameSet(routines) {
|
|
20
|
+
return new Set(routines.flatMap((routine) => boundaryLabels(routine)));
|
|
27
21
|
}
|
|
28
22
|
export function buildProfileSummaries(profileName) {
|
|
29
23
|
const profile = getRegisterCareProfile(profileName);
|
|
@@ -46,48 +40,13 @@ export function buildProfileSummaryLookup(profileName) {
|
|
|
46
40
|
return out;
|
|
47
41
|
}
|
|
48
42
|
export function routineNames(routines) {
|
|
49
|
-
return routines.flatMap((routine) => routine
|
|
43
|
+
return routines.flatMap((routine) => boundaryLabels(routine));
|
|
50
44
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
return contractMap.get(routine.name);
|
|
58
|
-
}
|
|
59
|
-
export function buildSummaries(routines, contractMap) {
|
|
60
|
-
const out = [];
|
|
61
|
-
const written = new Set();
|
|
62
|
-
for (const routine of routines) {
|
|
63
|
-
const inferred = inferRoutineSummary(routine);
|
|
64
|
-
const contract = entryContract(routine, contractMap);
|
|
65
|
-
out.push({
|
|
66
|
-
name: routine.name,
|
|
67
|
-
mayRead: unique([...inferred.mayRead, ...(contract?.in ?? [])]),
|
|
68
|
-
mayWrite: unique([
|
|
69
|
-
...inferred.mayWrite,
|
|
70
|
-
...(contract?.out ?? []),
|
|
71
|
-
...(contract?.clobbers ?? []),
|
|
72
|
-
]),
|
|
73
|
-
preserved: unique([...inferred.preserved, ...(contract?.preserves ?? [])]),
|
|
74
|
-
});
|
|
75
|
-
written.add(routine.name);
|
|
76
|
-
for (const alias of routine.entryLabels)
|
|
77
|
-
written.add(alias);
|
|
78
|
-
}
|
|
79
|
-
for (const [name, contract] of contractMap) {
|
|
80
|
-
if (written.has(name))
|
|
81
|
-
continue;
|
|
82
|
-
out.push({
|
|
83
|
-
name,
|
|
84
|
-
mayRead: [...contract.in],
|
|
85
|
-
mayWrite: [...contract.out, ...contract.clobbers],
|
|
86
|
-
preserved: [...contract.preserves],
|
|
87
|
-
});
|
|
88
|
-
written.add(name);
|
|
89
|
-
}
|
|
90
|
-
return out;
|
|
45
|
+
export function buildSummaries(routines, contractMap, profileSummaries = []) {
|
|
46
|
+
const names = routineNameSet(routines);
|
|
47
|
+
const routineSummaries = inferRoutineSummariesToFixedPoint([...routines], contractMap, names, [...profileSummaries]);
|
|
48
|
+
const summaries = routineSummaries.map((item) => item.summary);
|
|
49
|
+
return summariesWithExternalContracts(summaries, contractMap, names);
|
|
91
50
|
}
|
|
92
51
|
export function buildSummaryByName(routines, summaries, profileSummaries = []) {
|
|
93
52
|
const out = new Map();
|
|
@@ -103,7 +62,7 @@ export function buildSummaryByName(routines, summaries, profileSummaries = []) {
|
|
|
103
62
|
const routineSummary = byRoutine.get(routine.name);
|
|
104
63
|
if (routineSummary === undefined)
|
|
105
64
|
continue;
|
|
106
|
-
for (const alias of routine
|
|
65
|
+
for (const alias of boundaryLabels(routine)) {
|
|
107
66
|
out.set(alias, routineSummary);
|
|
108
67
|
}
|
|
109
68
|
}
|
|
@@ -118,28 +77,36 @@ export function withAcceptedOutputs(summaries, acceptedOutputCandidates) {
|
|
|
118
77
|
if (!accepted || accepted.length === 0) {
|
|
119
78
|
return summary;
|
|
120
79
|
}
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
80
|
+
const written = new Set(summary.mayWrite);
|
|
81
|
+
const promoted = accepted.filter((unit) => written.has(unit));
|
|
82
|
+
if (promoted.length === 0) {
|
|
83
|
+
return summary;
|
|
84
|
+
}
|
|
85
|
+
const valueRelations = [...summary.valueRelations];
|
|
86
|
+
for (const unit of promoted) {
|
|
87
|
+
if (!valueRelations.some((relation) => relation.out.includes(unit))) {
|
|
88
|
+
valueRelations.push({ out: [unit], from: [] });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { ...summary, valueRelations };
|
|
127
92
|
});
|
|
128
93
|
}
|
|
129
|
-
export function unknownBoundaryDiagnostics(
|
|
130
|
-
return
|
|
131
|
-
.filter((
|
|
132
|
-
.map((
|
|
94
|
+
export function unknownBoundaryDiagnostics(directBoundaries, knownRoutines) {
|
|
95
|
+
return directBoundaries
|
|
96
|
+
.filter((boundary) => !knownRoutines.has(boundary.target))
|
|
97
|
+
.map((boundary) => ({
|
|
133
98
|
severity: 'warning',
|
|
134
99
|
code: 'AZMN_REGISTER_CARE',
|
|
135
|
-
message: `Register-care cannot prove ${
|
|
136
|
-
sourceName:
|
|
137
|
-
line:
|
|
138
|
-
column:
|
|
100
|
+
message: `Register-care cannot prove ${boundary.subject}; add a routine body or .asmi extern contract.`,
|
|
101
|
+
sourceName: boundary.file,
|
|
102
|
+
line: boundary.line,
|
|
103
|
+
column: boundary.column,
|
|
139
104
|
}));
|
|
140
105
|
}
|
|
141
|
-
export function unknownCallList(
|
|
142
|
-
return unique(
|
|
106
|
+
export function unknownCallList(directBoundaries, knownRoutines) {
|
|
107
|
+
return unique(directBoundaries
|
|
108
|
+
.filter((boundary) => !knownRoutines.has(boundary.target))
|
|
109
|
+
.map((boundary) => boundary.target)).sort();
|
|
143
110
|
}
|
|
144
111
|
export function buildOutputCandidateFixability(routines, outputCandidates, autoFixableCandidateKeys) {
|
|
145
112
|
const autoFixable = autoFixableCandidateKeys([...routines], [...outputCandidates]);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RegisterCareRoutine, RoutineContract, RoutineSummary } from './types.js';
|
|
2
|
+
export declare function inferRoutineSummary(routine: RegisterCareRoutine, boundarySummaries?: ReadonlyMap<string, RoutineSummary>): RoutineSummary;
|
|
3
|
+
export declare function applyRoutineContract(summary: RoutineSummary, contract: RoutineContract): RoutineSummary;
|