@jhlagado/azm 0.2.12 → 0.2.14
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/dist/src/api-compile.d.ts +7 -1
- package/dist/src/api-compile.js +17 -5
- package/dist/src/api-register-contracts.js +69 -2
- package/dist/src/api-tooling.d.ts +1 -1
- package/dist/src/cli/artifact-files.d.ts +1 -0
- package/dist/src/cli/artifact-files.js +5 -0
- package/dist/src/cli/parse-args.d.ts +6 -1
- package/dist/src/cli/parse-args.js +59 -0
- package/dist/src/cli/run.js +2 -2
- package/dist/src/cli/usage.js +5 -0
- package/dist/src/cli/write-artifacts.d.ts +1 -1
- package/dist/src/cli/write-artifacts.js +15 -2
- package/dist/src/expansion/op-expansion.js +12 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/outputs/types.d.ts +13 -1
- package/dist/src/register-contracts/analyze-helpers.d.ts +6 -1
- package/dist/src/register-contracts/analyze-helpers.js +67 -0
- package/dist/src/register-contracts/analyze.d.ts +8 -1
- package/dist/src/register-contracts/analyze.js +353 -16
- package/dist/src/register-contracts/interfaceContracts.js +45 -0
- package/dist/src/register-contracts/liveness.js +23 -0
- package/dist/src/register-contracts/policy.d.ts +2 -0
- package/dist/src/register-contracts/policy.js +54 -0
- package/dist/src/register-contracts/profiles.d.ts +5 -0
- package/dist/src/register-contracts/profiles.js +32 -2
- package/dist/src/register-contracts/programModel-boundaries.d.ts +5 -1
- package/dist/src/register-contracts/programModel-boundaries.js +20 -5
- package/dist/src/register-contracts/programModel-routines.js +37 -6
- package/dist/src/register-contracts/ratchet.d.ts +3 -0
- package/dist/src/register-contracts/ratchet.js +88 -0
- package/dist/src/register-contracts/report.d.ts +8 -1
- package/dist/src/register-contracts/report.js +174 -0
- package/dist/src/register-contracts/smartCommentParsing.js +22 -0
- package/dist/src/register-contracts/summaries.js +4 -0
- package/dist/src/register-contracts/summary-boundary.js +21 -3
- package/dist/src/register-contracts/summary.js +31 -3
- package/dist/src/register-contracts/tooling.d.ts +2 -1
- package/dist/src/register-contracts/tooling.js +2 -0
- package/dist/src/register-contracts/types.d.ts +159 -0
- package/dist/src/syntax/parse-line.js +3 -0
- package/docs/codebase/02-source-loading-and-parsing.md +10 -6
- package/docs/codebase/04-ops-and-register-contracts.md +58 -4
- package/docs/codebase/05-interfaces-and-output-artifacts.md +69 -6
- package/docs/codebase/06-verification-and-maintenance.md +10 -2
- package/docs/codebase/appendices/a-directory-file-reference.md +3 -1
- package/docs/codebase/appendices/b-compile-flow-reference.md +7 -5
- package/docs/codebase/appendices/c-public-surface-reference.md +19 -5
- package/package.json +1 -1
|
@@ -91,7 +91,7 @@ function applyStackPop(tokens, consumedProduced, intendedProduced, stack, state,
|
|
|
91
91
|
}
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
|
-
if (popped.length !== units.length) {
|
|
94
|
+
if (popped.tokens.length !== units.length) {
|
|
95
95
|
for (const unit of units) {
|
|
96
96
|
tokens.set(unit, { origin: 'unknown' });
|
|
97
97
|
consumedProduced.delete(unit);
|
|
@@ -100,11 +100,35 @@ function applyStackPop(tokens, consumedProduced, intendedProduced, stack, state,
|
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
units.forEach((unit, idx) => {
|
|
103
|
-
tokens.set(unit, popped[idx] ?? { origin: 'unknown' });
|
|
103
|
+
tokens.set(unit, popped.tokens[idx] ?? { origin: 'unknown' });
|
|
104
104
|
consumedProduced.delete(unit);
|
|
105
105
|
intendedProduced.delete(unit);
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
function stackFrameUnits(frameUnit) {
|
|
109
|
+
if (frameUnit === 'AF')
|
|
110
|
+
return ['A', 'sign', 'zero', 'halfCarry', 'parity', 'carry'];
|
|
111
|
+
if (frameUnit === 'BC')
|
|
112
|
+
return ['B', 'C'];
|
|
113
|
+
if (frameUnit === 'DE')
|
|
114
|
+
return ['D', 'E'];
|
|
115
|
+
if (frameUnit === 'HL')
|
|
116
|
+
return ['H', 'L'];
|
|
117
|
+
if (frameUnit === 'IX')
|
|
118
|
+
return ['IXH', 'IXL'];
|
|
119
|
+
return ['IYH', 'IYL'];
|
|
120
|
+
}
|
|
121
|
+
function consumeKnownBoundaryStackFrame(stack, state, knownBoundary) {
|
|
122
|
+
for (const frameUnit of knownBoundary?.consumesStackFrame ?? []) {
|
|
123
|
+
const expected = stackFrameUnits(frameUnit);
|
|
124
|
+
const popped = stack.pop();
|
|
125
|
+
if (popped === undefined ||
|
|
126
|
+
popped.units.length !== expected.length ||
|
|
127
|
+
!popped.units.every((unit, index) => unit === expected[index])) {
|
|
128
|
+
state.stackBalanced = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
108
132
|
function applyUnknownStackUnits(tokens, consumedProduced, intendedProduced, units) {
|
|
109
133
|
for (const unit of units) {
|
|
110
134
|
tokens.set(unit, { origin: 'unknown' });
|
|
@@ -114,7 +138,10 @@ function applyUnknownStackUnits(tokens, consumedProduced, intendedProduced, unit
|
|
|
114
138
|
}
|
|
115
139
|
function applyStackEffect(tokens, consumedProduced, intendedProduced, stack, state, effect, expectedTerminalReturn, knownBoundary) {
|
|
116
140
|
if (effect.stack.kind === 'push') {
|
|
117
|
-
stack.push(
|
|
141
|
+
stack.push({
|
|
142
|
+
units: effect.stack.units,
|
|
143
|
+
tokens: effect.stack.units.map((unit) => readToken(tokens, unit)),
|
|
144
|
+
});
|
|
118
145
|
return;
|
|
119
146
|
}
|
|
120
147
|
if (effect.stack.kind === 'pop') {
|
|
@@ -126,6 +153,7 @@ function applyStackEffect(tokens, consumedProduced, intendedProduced, stack, sta
|
|
|
126
153
|
applyUnknownStackUnits(tokens, consumedProduced, intendedProduced, effect.stack.units);
|
|
127
154
|
return;
|
|
128
155
|
}
|
|
156
|
+
consumeKnownBoundaryStackFrame(stack, state, knownBoundary);
|
|
129
157
|
if (expectedTerminalReturn && stack.length !== 0) {
|
|
130
158
|
state.stackBalanced = false;
|
|
131
159
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Diagnostic } from '../model/diagnostic.js';
|
|
2
2
|
import type { LoadedProgram } from '../tooling/api.js';
|
|
3
|
-
import type { AnalyzeRegisterContractsOptions, RegisterContractsOutputCandidate, RegisterContractsUnit } from './types.js';
|
|
3
|
+
import type { AnalyzeRegisterContractsOptions, RegisterContractsFinding, RegisterContractsOutputCandidate, RegisterContractsUnit } from './types.js';
|
|
4
4
|
export interface RegisterContractsTextEdit {
|
|
5
5
|
file: string;
|
|
6
6
|
line: number;
|
|
@@ -42,6 +42,7 @@ export interface AnalyzeRegisterContractsForToolsOptions extends Omit<AnalyzeReg
|
|
|
42
42
|
export type AnalyzeRegisterCareForToolsOptions = AnalyzeRegisterContractsForToolsOptions;
|
|
43
43
|
export interface AnalyzeRegisterContractsForToolsResult {
|
|
44
44
|
diagnostics: Diagnostic[];
|
|
45
|
+
findings: RegisterContractsFinding[];
|
|
45
46
|
outputCandidates: RegisterContractsOutputCandidate[];
|
|
46
47
|
candidateDiagnostics: RegisterContractsCandidateDiagnostic[];
|
|
47
48
|
codeActions: RegisterContractsCodeAction[];
|
|
@@ -46,12 +46,14 @@ export function analyzeRegisterContractsForTools(loaded, options) {
|
|
|
46
46
|
? baseResultOptions
|
|
47
47
|
: { ...baseResultOptions, registerContractsProfile: profile });
|
|
48
48
|
const outputCandidates = result.outputCandidates ?? [];
|
|
49
|
+
const findings = result.findings ?? [];
|
|
49
50
|
const candidateDiagnostics = outputCandidates.map(diagnosticForOutputCandidate);
|
|
50
51
|
const codeActions = outputCandidates
|
|
51
52
|
.filter((candidate) => candidate.autoFixable === true)
|
|
52
53
|
.map(codeActionForOutputCandidate);
|
|
53
54
|
return {
|
|
54
55
|
diagnostics: result.diagnostics,
|
|
56
|
+
findings,
|
|
55
57
|
outputCandidates,
|
|
56
58
|
candidateDiagnostics,
|
|
57
59
|
codeActions,
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { Z80Instruction } from '../z80/instruction.js';
|
|
2
2
|
export type RegisterContractsMode = 'off' | 'audit' | 'warn' | 'error' | 'strict';
|
|
3
|
+
export type RegisterContractsReportFormat = 'text' | 'json';
|
|
4
|
+
export type RegisterContractsInferenceFormat = 'json' | 'markdown';
|
|
5
|
+
export type RegisterContractsPolicyMode = 'off' | 'audit' | 'strict';
|
|
6
|
+
export interface RegisterContractsPolicy {
|
|
7
|
+
strict?: readonly string[];
|
|
8
|
+
audit?: readonly string[];
|
|
9
|
+
off?: readonly string[];
|
|
10
|
+
}
|
|
3
11
|
/** @deprecated Use RegisterContractsMode. */
|
|
4
12
|
export type RegisterCareMode = RegisterContractsMode;
|
|
5
13
|
export type RegisterContractsUnit = 'A' | 'B' | 'C' | 'D' | 'E' | 'H' | 'L' | 'IXH' | 'IXL' | 'IYH' | 'IYL' | 'SPH' | 'SPL' | 'carry' | 'zero' | 'sign' | 'parity' | 'halfCarry';
|
|
14
|
+
export type RegisterContractsStackFrameUnit = 'AF' | 'BC' | 'DE' | 'HL' | 'IX' | 'IY';
|
|
6
15
|
/** @deprecated Use RegisterContractsUnit. */
|
|
7
16
|
export type RegisterCareUnit = RegisterContractsUnit;
|
|
8
17
|
export type SmartComment = {
|
|
@@ -28,6 +37,10 @@ export type SmartComment = {
|
|
|
28
37
|
kind: 'expectOut';
|
|
29
38
|
carriers: RegisterContractsUnit[];
|
|
30
39
|
name?: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'rcIgnoreNext';
|
|
42
|
+
findingKind: RegisterContractsFindingKind;
|
|
43
|
+
reason: string;
|
|
31
44
|
};
|
|
32
45
|
export interface LocatedSmartComment {
|
|
33
46
|
file: string;
|
|
@@ -47,6 +60,9 @@ export interface RegisterContractsInstruction {
|
|
|
47
60
|
file: string;
|
|
48
61
|
line: number;
|
|
49
62
|
column: number;
|
|
63
|
+
sourceUnit?: string;
|
|
64
|
+
sourceRelation?: 'entry' | 'include' | 'import';
|
|
65
|
+
sourceUnitRelation?: 'entry' | 'include' | 'import';
|
|
50
66
|
labels: string[];
|
|
51
67
|
constants?: ReadonlyMap<string, number>;
|
|
52
68
|
}
|
|
@@ -60,6 +76,9 @@ export interface RegisterContractsRoutine {
|
|
|
60
76
|
constants?: ReadonlyMap<string, number>;
|
|
61
77
|
span: {
|
|
62
78
|
file: string;
|
|
79
|
+
sourceUnit?: string;
|
|
80
|
+
sourceRelation?: 'entry' | 'include' | 'import';
|
|
81
|
+
sourceUnitRelation?: 'entry' | 'include' | 'import';
|
|
63
82
|
start: {
|
|
64
83
|
line: number;
|
|
65
84
|
column: number;
|
|
@@ -78,6 +97,9 @@ export interface RegisterContractsDirectCall {
|
|
|
78
97
|
file: string;
|
|
79
98
|
line: number;
|
|
80
99
|
column: number;
|
|
100
|
+
sourceUnit?: string;
|
|
101
|
+
sourceRelation?: 'entry' | 'include' | 'import';
|
|
102
|
+
sourceUnitRelation?: 'entry' | 'include' | 'import';
|
|
81
103
|
}
|
|
82
104
|
/** @deprecated Use RegisterContractsDirectCall. */
|
|
83
105
|
export type RegisterCareDirectCall = RegisterContractsDirectCall;
|
|
@@ -140,12 +162,17 @@ export interface RoutineSummary {
|
|
|
140
162
|
valueRelations: ValueRelation[];
|
|
141
163
|
stackBalanced: boolean;
|
|
142
164
|
hasUnknownStackEffect?: boolean;
|
|
165
|
+
consumesStackFrame?: RegisterContractsStackFrameUnit[];
|
|
143
166
|
outputCandidates?: RegisterContractsUnit[];
|
|
144
167
|
}
|
|
145
168
|
export interface RegisterContractsOutputCandidate {
|
|
169
|
+
kind?: 'output_candidate';
|
|
146
170
|
file: string;
|
|
147
171
|
line: number;
|
|
148
172
|
column: number;
|
|
173
|
+
sourceUnit?: string;
|
|
174
|
+
sourceRelation?: 'entry' | 'include' | 'import';
|
|
175
|
+
sourceUnitRelation?: 'entry' | 'include' | 'import';
|
|
149
176
|
routine: string;
|
|
150
177
|
carriers: RegisterContractsUnit[];
|
|
151
178
|
autoFixable?: boolean;
|
|
@@ -154,35 +181,167 @@ export interface RegisterContractsOutputCandidate {
|
|
|
154
181
|
/** @deprecated Use RegisterContractsOutputCandidate. */
|
|
155
182
|
export type RegisterCareOutputCandidate = RegisterContractsOutputCandidate;
|
|
156
183
|
export interface RegisterContractsConflict {
|
|
184
|
+
kind?: 'definite_contract_violation' | 'flag_lifetime_risk';
|
|
157
185
|
file: string;
|
|
158
186
|
line: number;
|
|
159
187
|
column: number;
|
|
188
|
+
sourceUnit?: string;
|
|
189
|
+
sourceRelation?: 'entry' | 'include' | 'import';
|
|
190
|
+
sourceUnitRelation?: 'entry' | 'include' | 'import';
|
|
191
|
+
routine?: string;
|
|
160
192
|
callTarget: string;
|
|
161
193
|
carriers: RegisterContractsUnit[];
|
|
162
194
|
message: string;
|
|
163
195
|
}
|
|
164
196
|
/** @deprecated Use RegisterContractsConflict. */
|
|
165
197
|
export type RegisterCareConflict = RegisterContractsConflict;
|
|
198
|
+
export type RegisterContractsFindingKind = 'definite_contract_violation' | 'flag_lifetime_risk' | 'missing_callee_contract' | 'unknown_control_flow' | 'external_interface_unknown' | 'output_candidate';
|
|
199
|
+
interface RegisterContractsFindingBase {
|
|
200
|
+
kind: RegisterContractsFindingKind;
|
|
201
|
+
file: string;
|
|
202
|
+
line: number;
|
|
203
|
+
column: number;
|
|
204
|
+
sourceUnit?: string;
|
|
205
|
+
sourceRelation?: 'entry' | 'include' | 'import';
|
|
206
|
+
sourceUnitRelation?: 'entry' | 'include' | 'import';
|
|
207
|
+
message: string;
|
|
208
|
+
routine?: string;
|
|
209
|
+
carriers?: RegisterContractsUnit[];
|
|
210
|
+
}
|
|
211
|
+
export interface RegisterContractsConflictFinding extends RegisterContractsFindingBase {
|
|
212
|
+
kind: 'definite_contract_violation' | 'flag_lifetime_risk';
|
|
213
|
+
callTarget: string;
|
|
214
|
+
}
|
|
215
|
+
export interface RegisterContractsUnknownBoundaryFinding extends RegisterContractsFindingBase {
|
|
216
|
+
kind: 'missing_callee_contract' | 'external_interface_unknown';
|
|
217
|
+
callTarget: string;
|
|
218
|
+
subject: string;
|
|
219
|
+
}
|
|
220
|
+
export interface RegisterContractsStackFinding extends RegisterContractsFindingBase {
|
|
221
|
+
kind: 'unknown_control_flow';
|
|
222
|
+
routine: string;
|
|
223
|
+
stackBalanced: boolean;
|
|
224
|
+
hasUnknownStackEffect?: boolean;
|
|
225
|
+
}
|
|
226
|
+
export interface RegisterContractsOutputCandidateFinding extends RegisterContractsFindingBase {
|
|
227
|
+
kind: 'output_candidate';
|
|
228
|
+
routine: string;
|
|
229
|
+
autoFixable?: boolean;
|
|
230
|
+
}
|
|
231
|
+
export type RegisterContractsFinding = RegisterContractsConflictFinding | RegisterContractsUnknownBoundaryFinding | RegisterContractsStackFinding | RegisterContractsOutputCandidateFinding;
|
|
166
232
|
export interface RegisterContractsReportModel {
|
|
167
233
|
entryFile: string;
|
|
168
234
|
mode: RegisterContractsMode;
|
|
169
235
|
profile?: string;
|
|
170
236
|
summaries: RoutineSummary[];
|
|
237
|
+
findings?: RegisterContractsFinding[];
|
|
238
|
+
suppressedFindings?: RegisterContractsSuppressedFinding[];
|
|
171
239
|
conflicts: RegisterContractsConflict[];
|
|
172
240
|
outputCandidates?: RegisterContractsOutputCandidate[];
|
|
173
241
|
unknownCalls: string[];
|
|
242
|
+
ratchet?: RegisterContractsRatchetResult;
|
|
243
|
+
}
|
|
244
|
+
export interface RegisterContractsJsonLocation {
|
|
245
|
+
file: string;
|
|
246
|
+
line: number;
|
|
247
|
+
column: number;
|
|
248
|
+
sourceUnit?: string;
|
|
249
|
+
sourceRelation?: 'entry' | 'include' | 'import';
|
|
250
|
+
sourceUnitRelation?: 'entry' | 'include' | 'import';
|
|
251
|
+
}
|
|
252
|
+
export interface RegisterContractsJsonRemediation {
|
|
253
|
+
category: 'add_contract' | 'fix_call_or_contract' | 'review_control_flow' | 'review_output_contract';
|
|
254
|
+
hint: string;
|
|
255
|
+
}
|
|
256
|
+
export interface RegisterContractsJsonFinding {
|
|
257
|
+
kind: RegisterContractsFindingKind;
|
|
258
|
+
location: RegisterContractsJsonLocation;
|
|
259
|
+
message: string;
|
|
260
|
+
routine?: string;
|
|
261
|
+
callTarget?: string;
|
|
262
|
+
subject?: string;
|
|
263
|
+
carriers?: RegisterContractsUnit[];
|
|
264
|
+
stackBalanced?: boolean;
|
|
265
|
+
hasUnknownStackEffect?: boolean;
|
|
266
|
+
autoFixable?: boolean;
|
|
267
|
+
remediation: RegisterContractsJsonRemediation;
|
|
268
|
+
}
|
|
269
|
+
export interface RegisterContractsSuppression {
|
|
270
|
+
file: string;
|
|
271
|
+
line: number;
|
|
272
|
+
column: number;
|
|
273
|
+
findingKind: RegisterContractsFindingKind;
|
|
274
|
+
reason: string;
|
|
275
|
+
}
|
|
276
|
+
export interface RegisterContractsSuppressedFinding {
|
|
277
|
+
finding: RegisterContractsFinding;
|
|
278
|
+
suppression: RegisterContractsSuppression;
|
|
279
|
+
}
|
|
280
|
+
export interface RegisterContractsJsonReportModel {
|
|
281
|
+
format: 'azm-register-contracts-report';
|
|
282
|
+
version: 1;
|
|
283
|
+
entryFile: string;
|
|
284
|
+
mode: RegisterContractsMode;
|
|
285
|
+
profile?: string;
|
|
286
|
+
summaries: RoutineSummary[];
|
|
287
|
+
findings: RegisterContractsJsonFinding[];
|
|
288
|
+
suppressedFindings?: Array<{
|
|
289
|
+
finding: RegisterContractsJsonFinding;
|
|
290
|
+
suppression: RegisterContractsSuppression;
|
|
291
|
+
}>;
|
|
292
|
+
unknownCalls: string[];
|
|
293
|
+
ratchet?: RegisterContractsRatchetResult;
|
|
294
|
+
}
|
|
295
|
+
export interface RegisterContractsInferenceRoutine {
|
|
296
|
+
name: string;
|
|
297
|
+
in: RegisterContractsUnit[];
|
|
298
|
+
out: RegisterContractsUnit[];
|
|
299
|
+
clobbers: RegisterContractsUnit[];
|
|
300
|
+
preserves: RegisterContractsUnit[];
|
|
301
|
+
confidence: 'explicit' | 'inferred' | 'draft';
|
|
302
|
+
callerImpact: {
|
|
303
|
+
outputCandidateCount: number;
|
|
304
|
+
outputCandidateCarriers: RegisterContractsUnit[];
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
export interface RegisterContractsInferenceModel {
|
|
308
|
+
format: 'azm-register-contracts-inference';
|
|
309
|
+
version: 1;
|
|
310
|
+
routines: RegisterContractsInferenceRoutine[];
|
|
311
|
+
}
|
|
312
|
+
export interface RegisterContractsRatchetEntry {
|
|
313
|
+
identity: string;
|
|
314
|
+
finding: RegisterContractsJsonFinding;
|
|
315
|
+
}
|
|
316
|
+
export interface RegisterContractsRatchetResult {
|
|
317
|
+
baselineFile?: string;
|
|
318
|
+
newFindings: RegisterContractsRatchetEntry[];
|
|
319
|
+
removedFindings: RegisterContractsRatchetEntry[];
|
|
320
|
+
changedFindings: Array<{
|
|
321
|
+
identity: string;
|
|
322
|
+
baseline: RegisterContractsJsonFinding;
|
|
323
|
+
current: RegisterContractsJsonFinding;
|
|
324
|
+
}>;
|
|
174
325
|
}
|
|
175
326
|
export interface AnalyzeRegisterContractsOptions {
|
|
176
327
|
mode: RegisterContractsMode;
|
|
328
|
+
policy?: RegisterContractsPolicy;
|
|
177
329
|
emitReport: boolean;
|
|
330
|
+
reportFormat?: RegisterContractsReportFormat;
|
|
178
331
|
emitInterface: boolean;
|
|
332
|
+
emitInference?: boolean;
|
|
333
|
+
inferenceFormat?: RegisterContractsInferenceFormat;
|
|
179
334
|
emitAnnotations?: boolean;
|
|
180
335
|
fixRegisterContracts?: boolean;
|
|
181
336
|
registerContractsProfile?: 'mon3';
|
|
182
337
|
interfaceContracts?: RoutineContract[];
|
|
183
338
|
acceptedOutputCandidates?: ReadonlyMap<string, RegisterContractsUnit[]>;
|
|
339
|
+
baselineReport?: RegisterContractsJsonReportModel;
|
|
340
|
+
baselineFile?: string;
|
|
341
|
+
ratchet?: boolean;
|
|
184
342
|
}
|
|
185
343
|
export interface RegisterContractsAnnotationFile {
|
|
186
344
|
readonly path: string;
|
|
187
345
|
readonly text: string;
|
|
188
346
|
}
|
|
347
|
+
export {};
|
|
@@ -67,6 +67,9 @@ function commentOnlyLine(line) {
|
|
|
67
67
|
column: firstNonWhitespaceColumn(line.text),
|
|
68
68
|
...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
|
|
69
69
|
...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
|
|
70
|
+
...(line.sourceUnitRelation !== undefined
|
|
71
|
+
? { sourceUnitRelation: line.sourceUnitRelation }
|
|
72
|
+
: {}),
|
|
70
73
|
},
|
|
71
74
|
},
|
|
72
75
|
],
|
|
@@ -107,10 +107,13 @@ been identified.
|
|
|
107
107
|
|
|
108
108
|
`src/source/logical-lines.ts` scans a `SourceFile` into `LogicalLine` objects. A
|
|
109
109
|
logical line records the source name, line number and original text. Tooling
|
|
110
|
-
loads can also attach `sourceUnit` and `
|
|
110
|
+
loads can also attach `sourceUnit`, `sourceRelation` and `sourceUnitRelation`:
|
|
111
111
|
|
|
112
112
|
- `sourceUnit` is the owning file for the current tooling unit
|
|
113
|
-
- `sourceRelation`
|
|
113
|
+
- `sourceRelation` says how this physical file entered the current expansion
|
|
114
|
+
step: `entry`, `include` or `import`
|
|
115
|
+
- `sourceUnitRelation` says how the owning source unit entered the load:
|
|
116
|
+
`entry` or `import`
|
|
114
117
|
|
|
115
118
|
This thin structure gives every later diagnostic a stable location and enough
|
|
116
119
|
provenance for tooling features that need to reason about module ownership.
|
|
@@ -176,10 +179,11 @@ line is a label, instruction, directive, layout declaration or comment item.
|
|
|
176
179
|
- comments
|
|
177
180
|
|
|
178
181
|
Each item carries a source span where appropriate. Tooling spans now preserve
|
|
179
|
-
optional `sourceUnit` and `
|
|
180
|
-
Assembly uses item kind to decide size and emission.
|
|
181
|
-
uses instruction, label and comment items to build
|
|
182
|
-
spans to connect emitted bytes back to files and
|
|
182
|
+
optional `sourceUnit`, `sourceRelation` and `sourceUnitRelation` fields when
|
|
183
|
+
the loader attached them. Assembly uses item kind to decide size and emission.
|
|
184
|
+
Register contract analysis uses instruction, label and comment items to build
|
|
185
|
+
routines. D8 map output uses spans to connect emitted bytes back to files and
|
|
186
|
+
lines.
|
|
183
187
|
|
|
184
188
|
## Top-Level Parse Order
|
|
185
189
|
|
|
@@ -168,7 +168,10 @@ without exposing them outside the module.
|
|
|
168
168
|
`src/register-contracts/smartComments.ts` reads AZMDoc comments from the comment maps
|
|
169
169
|
captured during loading. Comment-block splitting and token parsing live in
|
|
170
170
|
`smartCommentBlocks.ts` and `smartCommentParsing.ts`. External `.asmi`
|
|
171
|
-
contracts are parsed in `interfaceContracts.ts`.
|
|
171
|
+
contracts are parsed in `interfaceContracts.ts`. The same parser also accepts
|
|
172
|
+
inline suppression comments in the form
|
|
173
|
+
`;! rc-ignore-next <finding-kind>: <reason>` for the next finding at that
|
|
174
|
+
location.
|
|
172
175
|
|
|
173
176
|
Contracts can describe:
|
|
174
177
|
|
|
@@ -184,6 +187,11 @@ routine contract. Source comments attach to routines in the current program.
|
|
|
184
187
|
The compact source form can place several clauses on one `;!` line, for
|
|
185
188
|
example `;! in A; out A; clobbers F`.
|
|
186
189
|
|
|
190
|
+
`.asmi` parsing stays strict. Files may contain `extern` or `service rst`
|
|
191
|
+
boundaries, `in`, `out`, `clobbers` and `preserves` clauses, then `end`.
|
|
192
|
+
Comment lines are rejected so interface files stay machine-generated and
|
|
193
|
+
deterministic.
|
|
194
|
+
|
|
187
195
|
## Effects, Summaries and Liveness
|
|
188
196
|
|
|
189
197
|
Register contract analysis depends on `src/z80/effects.ts`. Effects describe which registers
|
|
@@ -212,10 +220,45 @@ summaries before the final pass. Strict mode uses `stackBalanced` and
|
|
|
212
220
|
`hasUnknownStackEffect` to distinguish balanced stack use from a routine whose
|
|
213
221
|
boundary may leave the stack in an unknown state.
|
|
214
222
|
|
|
223
|
+
Some monitor ABIs deliberately consume a caller-prepared stack frame at a known
|
|
224
|
+
boundary. The built-in MON3/TecMate profile models `RST $10` with `C=$53`
|
|
225
|
+
(`MON_BANK_CALL`) as a service-specific boundary that consumes the top stack
|
|
226
|
+
entries `AF`, `DE` and `HL`, returns `A` and carry from the banked target, and
|
|
227
|
+
leaves the caller stack balanced. This is not a blanket relaxation for `RST`;
|
|
228
|
+
the behavior is selected by the proven `C` service value. The same profile also
|
|
229
|
+
provides a `C >= $60` TecMate expansion-service range fallback that returns
|
|
230
|
+
`A` and carry for installed expansion services.
|
|
231
|
+
|
|
232
|
+
The analysis now emits typed findings rather than only free-form conflict text.
|
|
233
|
+
The current finding set covers:
|
|
234
|
+
|
|
235
|
+
- `definite_contract_violation`
|
|
236
|
+
- `flag_lifetime_risk`
|
|
237
|
+
- `missing_callee_contract`
|
|
238
|
+
- `external_interface_unknown`
|
|
239
|
+
- `unknown_control_flow`
|
|
240
|
+
- `output_candidate`
|
|
241
|
+
|
|
242
|
+
Policy and suppression handling operate on those finding kinds. Report JSON,
|
|
243
|
+
tooling consumers and baseline ratchets all use the same typed finding model.
|
|
244
|
+
|
|
245
|
+
`policy.ts` resolves per-file register-contract modes from `strict`, `audit`
|
|
246
|
+
and `off` glob lists. Specific patterns win over broad patterns, then stricter
|
|
247
|
+
matches win ties. This lets one compile run audit imported wrappers while
|
|
248
|
+
keeping core files in strict mode or disabling analysis for known external
|
|
249
|
+
shims. When policy disables a file, unresolved call targets in that file no
|
|
250
|
+
longer surface as ordinary unknown-symbol diagnostics.
|
|
251
|
+
|
|
215
252
|
## Reports, Interfaces and Tooling
|
|
216
253
|
|
|
217
|
-
`report.ts` renders human-readable `.regcontracts.txt` reports
|
|
218
|
-
|
|
254
|
+
`report.ts` renders human-readable `.regcontracts.txt` reports, JSON report
|
|
255
|
+
payloads, `.asmi` interface metadata and review-oriented inference exports. The
|
|
256
|
+
text and JSON reports share one model with routine summaries, findings,
|
|
257
|
+
suppressed findings, unknown calls and optional ratchet results. Inference
|
|
258
|
+
exports are lighter-weight routine summaries for review workflows and can be
|
|
259
|
+
rendered as JSON or Markdown.
|
|
260
|
+
|
|
261
|
+
`report.ts` also renders generated source contracts as one compact `;!` line,
|
|
219
262
|
joining `in`, `out`, `maybe-out` and `clobbers` clauses with semicolons.
|
|
220
263
|
When a routine may clobber the full flag set, the source form uses `F` as the
|
|
221
264
|
compact carrier name. `annotate.ts`, `annotations.ts`, `fix.ts` and
|
|
@@ -225,7 +268,12 @@ conservative fixes.
|
|
|
225
268
|
The CLI can request these behaviours through:
|
|
226
269
|
|
|
227
270
|
- `--reg-report`
|
|
271
|
+
- `--reg-report-format`
|
|
272
|
+
- `--reg-baseline`
|
|
273
|
+
- `--reg-ratchet`
|
|
228
274
|
- `--reg-interface`
|
|
275
|
+
- `--reg-infer`
|
|
276
|
+
- `--reg-infer-format`
|
|
229
277
|
- `--contracts`
|
|
230
278
|
- `--fix`
|
|
231
279
|
- `--accept-out`
|
|
@@ -236,6 +284,11 @@ file, line, column, message, fixability and optional text edits. An editor can
|
|
|
236
284
|
show the same register contract information that the CLI reports while using
|
|
237
285
|
normal editor actions for accepted fixes.
|
|
238
286
|
|
|
287
|
+
`analyzeRegisterContractsForTools()` now also returns the structured findings
|
|
288
|
+
list and output candidate list beside candidate diagnostics and code actions, so
|
|
289
|
+
tooling integrations can render the same categorised analysis that the CLI
|
|
290
|
+
writes to report artifacts.
|
|
291
|
+
|
|
239
292
|
## Changing Ops or Register Contracts
|
|
240
293
|
|
|
241
294
|
Op changes belong in `src/expansion/`, with tests under
|
|
@@ -249,7 +302,8 @@ Register contract changes usually begin in one of these files:
|
|
|
249
302
|
`instruction-operands.ts`, `instruction-predicates.ts`
|
|
250
303
|
- Summary inference: `summary.ts`
|
|
251
304
|
- Caller liveness: `liveness.ts`
|
|
252
|
-
-
|
|
305
|
+
- Policy selection and ratchets: `policy.ts`, `ratchet.ts`
|
|
306
|
+
- Output text and structured exports: `report.ts`
|
|
253
307
|
- Source edits: `annotate.ts`, `fix.ts`, `annotations.ts`
|
|
254
308
|
- Tooling surface: `tooling.ts`
|
|
255
309
|
|
|
@@ -68,6 +68,14 @@ when artifact writing succeeds.
|
|
|
68
68
|
artifact suppression, include paths, source-root, case-style linting, directive
|
|
69
69
|
aliases and register contract options.
|
|
70
70
|
|
|
71
|
+
Register-contract parsing now includes:
|
|
72
|
+
|
|
73
|
+
- report format selection with `--reg-report-format text|json`
|
|
74
|
+
- baseline comparison with `--reg-baseline <report.json>`
|
|
75
|
+
- ratcheting with `--reg-ratchet`
|
|
76
|
+
- inference export with `--reg-infer`
|
|
77
|
+
- inference format selection with `--reg-infer-format json|markdown`
|
|
78
|
+
|
|
71
79
|
`src/cli/write-artifacts.ts` maps parsed options into
|
|
72
80
|
`CompileNextFunctionOptions` and calculates the output stem.
|
|
73
81
|
`src/cli/artifact-files.ts` writes in-memory artifacts to disk. If the user
|
|
@@ -76,6 +84,13 @@ path and side artifacts use the same base. If the user supplies only
|
|
|
76
84
|
`program.asm`, AZM writes outputs next to the entry source using the source
|
|
77
85
|
stem.
|
|
78
86
|
|
|
87
|
+
Register-contract side artifacts now use suffixes derived from the selected
|
|
88
|
+
format:
|
|
89
|
+
|
|
90
|
+
- `.regcontracts.txt` or `.regcontracts.json` for reports
|
|
91
|
+
- `.asmi` for inferred interfaces
|
|
92
|
+
- `.regcontracts.inference.json` or `.regcontracts.inference.md` for review exports
|
|
93
|
+
|
|
79
94
|
## Compile API
|
|
80
95
|
|
|
81
96
|
`src/api-compile.ts` exports:
|
|
@@ -103,8 +118,14 @@ Important options include:
|
|
|
103
118
|
| `d8mInputs` | Artifact paths recorded in D8 metadata. |
|
|
104
119
|
| `emitBin`, `emitHex`, `emitD8m`, `emitAsm80` | Artifact selection. |
|
|
105
120
|
| `registerContracts` | Register contract mode. |
|
|
106
|
-
| `
|
|
121
|
+
| `registerContractsPolicy` | Per-file strict, audit and off policy. |
|
|
122
|
+
| `emitRegisterReport` | Emit text or JSON report artifact. |
|
|
123
|
+
| `registerContractsReportFormat` | Report format, `text` or `json`. |
|
|
124
|
+
| `registerContractsBaseline` | Baseline JSON report for ratcheting. |
|
|
125
|
+
| `registerContractsRatchet` | Fail when findings increase. |
|
|
107
126
|
| `emitRegisterInterface` | Emit `.asmi` artifact. |
|
|
127
|
+
| `emitRegisterInference` | Emit inference review artifact. |
|
|
128
|
+
| `registerContractsInferenceFormat` | Inference format, `json` or `markdown`. |
|
|
108
129
|
| `emitRegisterAnnotations` | Emit source annotation artifact. |
|
|
109
130
|
| `fixRegisterContracts` | Apply conservative source fixes. |
|
|
110
131
|
| `acceptRegisterOutputCandidates` | Promote selected output candidates. |
|
|
@@ -112,6 +133,19 @@ Important options include:
|
|
|
112
133
|
| `registerContractsInterfaces` | External `.asmi` contract files. |
|
|
113
134
|
| `skipAssembly` | Run loading and analysis only. |
|
|
114
135
|
|
|
136
|
+
`registerContractsPolicy` matches the physical source file recorded on each
|
|
137
|
+
register-contract routine, direct call and finding. In a single assembled
|
|
138
|
+
translation unit, included files remain distinct physical files for diagnostics
|
|
139
|
+
and policy matching. For example, if `monitor.asm` includes `rtc.asm` and
|
|
140
|
+
`disassembler.asm`, a finding owned by `rtc.asm` is matched against `rtc.asm`,
|
|
141
|
+
not only against the root `monitor.asm` path.
|
|
142
|
+
|
|
143
|
+
This is independent of source ownership units: `.include` keeps the surrounding
|
|
144
|
+
source ownership unit for import/visibility semantics, but policy matching uses
|
|
145
|
+
the physical `sourceName`/finding file. This allows projects to audit retained
|
|
146
|
+
legacy source one included file at a time while keeping the whole program in one
|
|
147
|
+
assembled unit.
|
|
148
|
+
|
|
115
149
|
`compile()` returns:
|
|
116
150
|
|
|
117
151
|
```ts
|
|
@@ -125,6 +159,11 @@ Diagnostics describe every warning or error observed during loading, analysis,
|
|
|
125
159
|
register contract analysis, assembly or artifact creation. Artifacts contain the
|
|
126
160
|
in-memory outputs requested by options.
|
|
127
161
|
|
|
162
|
+
When analysis is enabled, `compile()` can now suppress ordinary unresolved-call
|
|
163
|
+
diagnostics for files whose register-contract policy resolves to `off`. That
|
|
164
|
+
keeps policy-controlled external boundaries in the register-contract report path
|
|
165
|
+
rather than duplicating them as symbol failures.
|
|
166
|
+
|
|
128
167
|
The older option names `registerCare`, `registerCareProfile` and
|
|
129
168
|
`registerCareInterfaces` remain as deprecated aliases for package consumers.
|
|
130
169
|
New callers should use the `registerContracts...` names.
|
|
@@ -138,17 +177,29 @@ helpers.
|
|
|
138
177
|
`loadProgramNext()` returns a loaded program with source items, source texts and
|
|
139
178
|
source line comments. `analyzeProgramNext()` runs semantic checks and returns
|
|
140
179
|
symbols. `analyzeRegisterContractsForTools()` returns register contract
|
|
141
|
-
diagnostics and code actions in a form
|
|
180
|
+
diagnostics, typed findings, output candidates and code actions in a form
|
|
181
|
+
suitable for editors.
|
|
142
182
|
|
|
143
183
|
The tooling loader now recognises both `.include` and `.import`. Both directives
|
|
144
184
|
flatten source into one parse stream. `.import` also marks parsed spans with a
|
|
145
185
|
new source ownership unit, while `.include` keeps the surrounding owner's unit.
|
|
146
|
-
Editor features can use `item.span.sourceUnit
|
|
147
|
-
|
|
186
|
+
Editor features can use `item.span.sourceUnit`, `item.span.sourceRelation` and
|
|
187
|
+
`item.span.sourceUnitRelation` together: `sourceRelation` tracks the physical
|
|
188
|
+
file edge that produced the parsed item, while `sourceUnitRelation` tracks
|
|
189
|
+
whether the owning unit entered as the entry source or through `.import`.
|
|
148
190
|
Diagnostics, symbols and source segments continue to use physical source file
|
|
149
191
|
paths, so imported files appear as their own files in editor and Debug80 map
|
|
150
192
|
metadata.
|
|
151
193
|
|
|
194
|
+
`analyzeProgramNext()` now also enforces the completed import-visibility pass.
|
|
195
|
+
Visibility checks walk instruction operands, data expressions, equates,
|
|
196
|
+
alignment and bin-range expressions and bare `.ds` size expressions. Imported
|
|
197
|
+
private labels remain visible inside their own source unit, including op
|
|
198
|
+
generated references and text included into that same unit. When a name already
|
|
199
|
+
collides with an equate, enum member or case-insensitive type or enum
|
|
200
|
+
declaration, assembly keeps the duplicate-symbol or duplicate-type diagnostic
|
|
201
|
+
instead of replacing it with a visibility error.
|
|
202
|
+
|
|
152
203
|
An editor integration usually starts with:
|
|
153
204
|
|
|
154
205
|
```ts
|
|
@@ -179,6 +230,7 @@ The output layer uses structured artifact objects from `src/outputs/types.ts`:
|
|
|
179
230
|
- `Asm80Artifact`
|
|
180
231
|
- `RegisterContractsReportArtifact`
|
|
181
232
|
- `RegisterContractsInterfaceArtifact`
|
|
233
|
+
- `RegisterContractsInferenceArtifact`
|
|
182
234
|
- `RegisterContractsAnnotationsArtifact`
|
|
183
235
|
|
|
184
236
|
Each artifact has a `kind` field. Callers can switch on `kind` to find the
|
|
@@ -241,11 +293,22 @@ supported.
|
|
|
241
293
|
|
|
242
294
|
Register contract report, interface and annotation artifacts are created through
|
|
243
295
|
`runRegisterContracts()` in `src/api-register-contracts.ts` and flow through
|
|
244
|
-
the same compile result and CLI write path.
|
|
245
|
-
interface is metadata that can be loaded by later compile runs through
|
|
296
|
+
the same compile result and CLI write path. Reports can be human-readable text
|
|
297
|
+
or JSON. The `.asmi` interface is metadata that can be loaded by later compile runs through
|
|
246
298
|
`--interface`. Annotation artifacts write source files when `--contracts` or
|
|
247
299
|
`--fix` is used.
|
|
248
300
|
|
|
301
|
+
The register-contract report artifact now carries optional structured payloads:
|
|
302
|
+
`format`, `json` and the active `findings` list. JSON reports use the stable
|
|
303
|
+
`azm-register-contracts-report` envelope with summaries, findings, suppressed
|
|
304
|
+
findings, unknown calls and optional ratchet results.
|
|
305
|
+
|
|
306
|
+
Inference artifacts carry a routine-oriented `azm-register-contracts-inference`
|
|
307
|
+
model. Each routine records inferred `in`, `out`, `clobbers` and `preserves`
|
|
308
|
+
carriers, an inference confidence and caller-impact counts for output
|
|
309
|
+
candidates. Markdown rendering is intended for review, while JSON is suitable
|
|
310
|
+
for automation and regression tooling.
|
|
311
|
+
|
|
249
312
|
## Public API Compatibility
|
|
250
313
|
|
|
251
314
|
The public API is defined by package exports and exported TypeScript types.
|
|
@@ -55,8 +55,9 @@ between modules rather than inside one helper.
|
|
|
55
55
|
|
|
56
56
|
Source-loading and tooling provenance changes belong here as well. The
|
|
57
57
|
`stage-11-tooling-api.test.ts` integration suite covers include and import
|
|
58
|
-
resolution, recursive load failures
|
|
59
|
-
|
|
58
|
+
resolution, recursive load failures, span ownership metadata and import
|
|
59
|
+
visibility analysis across direct calls, jump fixups, data expressions, bare
|
|
60
|
+
`.ds` sizes, duplicate-symbol precedence and include-only wrappers.
|
|
60
61
|
|
|
61
62
|
## CLI, ASM80 and Differential Tests
|
|
62
63
|
|
|
@@ -65,6 +66,10 @@ failure modes, determinism, case-style linting and register contract switches.
|
|
|
65
66
|
Users experience the command-line behaviour through argument parsing,
|
|
66
67
|
diagnostics, output paths and exit status.
|
|
67
68
|
|
|
69
|
+
The register-contract CLI suite now also covers JSON report output, inference
|
|
70
|
+
artifact output, baseline loading and ratchet failures so option parsing and
|
|
71
|
+
artifact suffix rules stay stable.
|
|
72
|
+
|
|
68
73
|
CLI tests also protect deterministic output. `compareDiagnosticsForCli()` sorts
|
|
69
74
|
diagnostics by file, line, column, severity, code and message. A CLI test can
|
|
70
75
|
catch changes that leave the compiler correct but make terminal output unstable.
|
|
@@ -153,6 +158,8 @@ Ask what kind of fact the change affects:
|
|
|
153
158
|
- Instruction-chain splitting belongs in `source/`, while inline source
|
|
154
159
|
generation and op template expansion belong in `expansion/`.
|
|
155
160
|
- Routine contracts and liveness belong in `register-contracts/`.
|
|
161
|
+
- Register-contract policy matching, suppression parsing and ratchets also
|
|
162
|
+
belong in `register-contracts/`.
|
|
156
163
|
- Artifact shape belongs in `outputs/`.
|
|
157
164
|
- User commands belong in `cli/`.
|
|
158
165
|
- Package consumers belong in `api-compile.ts`, `api-tooling.ts` and
|
|
@@ -187,6 +194,7 @@ This book should change when:
|
|
|
187
194
|
- public package exports change
|
|
188
195
|
- CLI option groups change
|
|
189
196
|
- output artifact shapes change
|
|
197
|
+
- register-contract finding kinds, policy rules or report schemas change
|
|
190
198
|
- a major subsystem gains a new responsibility
|
|
191
199
|
- tests or guardrails are reorganised
|
|
192
200
|
|