@jhlagado/azm 0.2.5 → 0.2.7

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.
@@ -1,2 +1,3 @@
1
1
  import type { RegisterCareInstruction } from './types.js';
2
2
  export declare function precedingCServiceName(item: RegisterCareInstruction | undefined): string | undefined;
3
+ export declare function precedingRegisterImmediateValue(item: RegisterCareInstruction | undefined, register: string): number | undefined;
@@ -9,3 +9,72 @@ export function precedingCServiceName(item) {
9
9
  }
10
10
  return undefined;
11
11
  }
12
+ function evaluateKnownConstant(expression, constants) {
13
+ switch (expression.kind) {
14
+ case 'number':
15
+ return expression.value;
16
+ case 'symbol':
17
+ return constants.get(expression.name);
18
+ case 'unary': {
19
+ const value = evaluateKnownConstant(expression.expression, constants);
20
+ if (value === undefined)
21
+ return undefined;
22
+ switch (expression.operator) {
23
+ case '+':
24
+ return value;
25
+ case '-':
26
+ return -value;
27
+ case '~':
28
+ return ~value;
29
+ }
30
+ }
31
+ case 'binary': {
32
+ const left = evaluateKnownConstant(expression.left, constants);
33
+ const right = evaluateKnownConstant(expression.right, constants);
34
+ if (left === undefined || right === undefined)
35
+ return undefined;
36
+ switch (expression.operator) {
37
+ case '+':
38
+ return left + right;
39
+ case '-':
40
+ return left - right;
41
+ case '*':
42
+ return left * right;
43
+ case '/':
44
+ return right === 0 ? undefined : Math.trunc(left / right);
45
+ case '%':
46
+ return right === 0 ? undefined : left % right;
47
+ case '&':
48
+ return left & right;
49
+ case '^':
50
+ return left ^ right;
51
+ case '|':
52
+ return left | right;
53
+ case '<<':
54
+ return left << right;
55
+ case '>>':
56
+ return left >> right;
57
+ }
58
+ }
59
+ case 'byte-function': {
60
+ const value = evaluateKnownConstant(expression.expression, constants);
61
+ if (value === undefined)
62
+ return undefined;
63
+ return expression.function === 'LSB' ? value & 0xff : (value >> 8) & 0xff;
64
+ }
65
+ default:
66
+ return undefined;
67
+ }
68
+ }
69
+ export function precedingRegisterImmediateValue(item, register) {
70
+ const instruction = item?.instruction;
71
+ if (!instruction || instruction.mnemonic !== 'ld')
72
+ return undefined;
73
+ if (instruction.target?.kind !== 'reg8' ||
74
+ instruction.target.register !== register.toLowerCase()) {
75
+ return undefined;
76
+ }
77
+ if (instruction.source.kind !== 'imm')
78
+ return undefined;
79
+ return evaluateKnownConstant(instruction.source.expression, item.constants ?? new Map());
80
+ }
@@ -1,7 +1,7 @@
1
1
  import { getZ80InstructionEffect } from '../z80/effects.js';
2
- import { precedingCServiceName } from './boundaryHints.js';
2
+ import { precedingCServiceName, precedingRegisterImmediateValue } from './boundaryHints.js';
3
3
  import { instructionSuccessors, labelIndex } from './controlFlow.js';
4
- import { rstServiceTargetName, rstTargetName } from './profiles.js';
4
+ import { rstDispatcherServiceTargetNames, rstServiceTargetName, rstTargetName, } from './profiles.js';
5
5
  function unique(units) {
6
6
  return [...new Set(units)];
7
7
  }
@@ -27,10 +27,13 @@ function boundaryTarget(routine, index, effect) {
27
27
  }
28
28
  if (effect.control.kind === 'rst' && effect.control.vector !== undefined) {
29
29
  const target = rstTargetName(effect.control.vector);
30
+ const previous = routine.instructions[index - 1];
30
31
  const service = precedingCServiceName(routine.instructions[index - 1]);
31
- const targets = service
32
- ? [rstServiceTargetName(effect.control.vector, service), target]
33
- : [target];
32
+ const targets = [
33
+ ...rstDispatcherServiceTargetNames(effect.control.vector, (register) => precedingRegisterImmediateValue(previous, register)),
34
+ ...(service ? [rstServiceTargetName(effect.control.vector, service)] : []),
35
+ target,
36
+ ];
34
37
  return { targets, conditional: false, subject: target };
35
38
  }
36
39
  return undefined;
@@ -1,9 +1,14 @@
1
- import type { RoutineSummary } from './types.js';
1
+ import type { RegisterCareUnit, RoutineSummary } from './types.js';
2
2
  export interface RegisterCareProfileSummary {
3
3
  name: 'mon3';
4
4
  rst: Map<number, RoutineSummary>;
5
5
  rstServices: Map<string, RoutineSummary>;
6
+ rstDispatchers: Map<number, {
7
+ selector: RegisterCareUnit;
8
+ services: Map<number, RoutineSummary>;
9
+ }>;
6
10
  }
7
11
  export declare function rstTargetName(vector: number): string;
8
12
  export declare function rstServiceTargetName(vector: number, service: string): string;
13
+ export declare function rstDispatcherServiceTargetNames(vector: number, selectorValue: (register: RegisterCareUnit) => number | undefined): string[];
9
14
  export declare function getRegisterCareProfile(name: 'mon3' | undefined): RegisterCareProfileSummary | undefined;
@@ -8,9 +8,166 @@ function normalizeServiceName(raw) {
8
8
  export function rstServiceTargetName(vector, service) {
9
9
  return `${rstTargetName(vector)}:${normalizeServiceName(service)}`;
10
10
  }
11
+ function mon3ApiTargetName(api, name) {
12
+ return `MON3_API_${api}_${name.replace(/[^A-Za-z0-9_]/gu, '').toUpperCase()}`;
13
+ }
14
+ function conservativeMon3ApiSummary(api, name) {
15
+ return {
16
+ name: mon3ApiTargetName(api, name),
17
+ mayRead: ['A', 'B', 'C', 'D', 'E', 'H', 'L', ...FLAG_UNITS],
18
+ mayWrite: ['A', 'B', 'C', 'D', 'E', 'H', 'L', ...FLAG_UNITS],
19
+ mayOutput: [],
20
+ preserved: [],
21
+ valueRelations: [],
22
+ stackBalanced: true,
23
+ hasUnknownStackEffect: false,
24
+ };
25
+ }
26
+ function mon3ApiServices(overrides) {
27
+ const names = [
28
+ 'SOFTWARE_ID',
29
+ 'VERSION_ID',
30
+ 'PRE_INIT',
31
+ 'BEEP_ALWAYS',
32
+ 'CONV_A_TO_SEG',
33
+ 'REG_A_TO_ASCII',
34
+ 'ASCII_TO_SEGMENT',
35
+ 'STRING_COMPARE',
36
+ 'HL_TO_STRING',
37
+ 'A_TO_STRING',
38
+ 'SCAN_SEGMENTS',
39
+ 'DISPLAY_ERROR',
40
+ 'LCD_BUSY',
41
+ 'STRING_TO_LCD',
42
+ 'CHAR_TO_LCD',
43
+ 'COMMAND_TO_LCD',
44
+ 'SCAN_KEYS',
45
+ 'SCAN_KEYS_WAIT',
46
+ 'MATRIX_SCAN',
47
+ 'JOYSTICK_SCAN',
48
+ 'SERIAL_ENABLE',
49
+ 'SERIAL_DISABLE',
50
+ 'TX_BYTE',
51
+ 'RX_BYTE',
52
+ 'INTEL_HEX_LOAD',
53
+ 'SEND_TO_SERIAL_API',
54
+ 'RECEIVE_FROM_SERIAL_API',
55
+ 'SEND_ASSEMBLY_API',
56
+ 'SEND_HEX_API',
57
+ 'GEN_DATA_DUMP',
58
+ 'CHECK_START_END',
59
+ 'MENU_DRIVER',
60
+ 'PARAM_DRIVER',
61
+ 'TIME_DELAY',
62
+ 'PLAY_NOTE',
63
+ 'PLAY_TUNE',
64
+ 'PLAY_TUNE_MENU',
65
+ 'GET_CAPS',
66
+ 'GET_SHADOW',
67
+ 'GET_PROTECT',
68
+ 'GET_EXPAND',
69
+ 'SET_CAPS',
70
+ 'SET_SHADOW',
71
+ 'SET_PROTECT',
72
+ 'SET_EXPAND',
73
+ 'STRING_TO_SERIAL',
74
+ 'RTC_API',
75
+ 'MENU_POP',
76
+ 'TOGGLE_CAPS',
77
+ 'RANDOM',
78
+ 'SET_DIS_START',
79
+ 'GET_DIS_NEXT',
80
+ 'GET_DISASSEMBLY',
81
+ 'MATRIX_SCAN_ASCII',
82
+ 'PARSE_MATRIX_SCAN',
83
+ 'LCD_CONFIRM',
84
+ 'GET_GLCD_TERM',
85
+ 'SET_GLCD_TERM',
86
+ 'LOAD_FROM_DISK',
87
+ 'OPEN_FILE',
88
+ 'READ_SECTOR',
89
+ 'WRITE_SECTOR',
90
+ 'RGB_SCAN',
91
+ ];
92
+ return new Map(names.map((serviceName, api) => [
93
+ api,
94
+ overrides.get(api) ?? conservativeMon3ApiSummary(api, serviceName),
95
+ ]));
96
+ }
97
+ export function rstDispatcherServiceTargetNames(vector, selectorValue) {
98
+ const mon3 = getRegisterCareProfile('mon3');
99
+ const dispatcher = mon3?.rstDispatchers.get(vector);
100
+ if (dispatcher === undefined)
101
+ return [];
102
+ const value = selectorValue(dispatcher.selector);
103
+ if (value === undefined)
104
+ return [];
105
+ const service = dispatcher.services.get(value);
106
+ return service ? [service.name] : [];
107
+ }
11
108
  export function getRegisterCareProfile(name) {
12
109
  if (name !== 'mon3')
13
110
  return undefined;
111
+ const matrixScan = {
112
+ name: mon3ApiTargetName(18, 'MATRIX_SCAN'),
113
+ mayRead: ['C'],
114
+ mayWrite: ['A', 'B', 'C', 'D', 'E', 'H', 'L', ...FLAG_UNITS],
115
+ mayOutput: ['D', 'E', 'zero'],
116
+ preserved: [],
117
+ valueRelations: [{ out: ['D', 'E', 'zero'], from: [] }],
118
+ stackBalanced: true,
119
+ hasUnknownStackEffect: false,
120
+ };
121
+ const stringToLcd = {
122
+ name: mon3ApiTargetName(13, 'STRING_TO_LCD'),
123
+ mayRead: ['C', 'H', 'L'],
124
+ mayWrite: ['A', 'H', 'L', ...FLAG_UNITS],
125
+ mayOutput: [],
126
+ preserved: ['B', 'C', 'D', 'E'],
127
+ valueRelations: [],
128
+ stackBalanced: true,
129
+ hasUnknownStackEffect: false,
130
+ };
131
+ const charToLcd = {
132
+ name: mon3ApiTargetName(14, 'CHAR_TO_LCD'),
133
+ mayRead: ['A', 'C'],
134
+ mayWrite: [],
135
+ mayOutput: [],
136
+ preserved: ['A', 'B', 'C', 'D', 'E', 'H', 'L', ...FLAG_UNITS],
137
+ valueRelations: [],
138
+ stackBalanced: true,
139
+ hasUnknownStackEffect: false,
140
+ };
141
+ const commandToLcd = {
142
+ name: mon3ApiTargetName(15, 'COMMAND_TO_LCD'),
143
+ mayRead: ['B', 'C'],
144
+ mayWrite: [],
145
+ mayOutput: [],
146
+ preserved: ['A', 'B', 'C', 'D', 'E', 'H', 'L', ...FLAG_UNITS],
147
+ valueRelations: [],
148
+ stackBalanced: true,
149
+ hasUnknownStackEffect: false,
150
+ };
151
+ const scanKeys = {
152
+ name: mon3ApiTargetName(16, 'SCAN_KEYS'),
153
+ mayRead: ['C'],
154
+ mayWrite: ['A', 'carry', 'zero'],
155
+ mayOutput: ['A', 'carry', 'zero'],
156
+ preserved: ['B', 'C', 'H', 'L'],
157
+ valueRelations: [{ out: ['A', 'carry', 'zero'], from: [] }],
158
+ stackBalanced: true,
159
+ hasUnknownStackEffect: false,
160
+ };
161
+ const parseMatrixScan = {
162
+ name: mon3ApiTargetName(54, 'PARSE_MATRIX_SCAN'),
163
+ mayRead: ['C', 'D', 'E', 'zero'],
164
+ mayWrite: ['A', 'B', 'C', 'H', 'L', 'carry', 'sign', 'parity', 'halfCarry'],
165
+ mayOutput: ['A', 'carry'],
166
+ preserved: ['D', 'E'],
167
+ valueRelations: [{ out: ['A', 'carry'], from: ['D', 'E', 'zero'] }],
168
+ stackBalanced: true,
169
+ hasUnknownStackEffect: false,
170
+ };
14
171
  return {
15
172
  name: 'mon3',
16
173
  rst: new Map([
@@ -43,5 +200,21 @@ export function getRegisterCareProfile(name) {
43
200
  },
44
201
  ],
45
202
  ]),
203
+ rstDispatchers: new Map([
204
+ [
205
+ 0x10,
206
+ {
207
+ selector: 'C',
208
+ services: mon3ApiServices(new Map([
209
+ [13, stringToLcd],
210
+ [14, charToLcd],
211
+ [15, commandToLcd],
212
+ [16, scanKeys],
213
+ [18, matrixScan],
214
+ [54, parseMatrixScan],
215
+ ])),
216
+ },
217
+ ],
218
+ ]),
46
219
  };
47
220
  }
@@ -4,6 +4,80 @@ function isGlobalLabel(name) {
4
4
  function routineNameFromExpression(expression) {
5
5
  return expression.kind === 'symbol' ? expression.name : undefined;
6
6
  }
7
+ function evaluateConstantExpression(expression, constants) {
8
+ switch (expression.kind) {
9
+ case 'number':
10
+ return expression.value;
11
+ case 'symbol':
12
+ return constants.get(expression.name);
13
+ case 'unary': {
14
+ const value = evaluateConstantExpression(expression.expression, constants);
15
+ if (value === undefined)
16
+ return undefined;
17
+ switch (expression.operator) {
18
+ case '+':
19
+ return value;
20
+ case '-':
21
+ return -value;
22
+ case '~':
23
+ return ~value;
24
+ }
25
+ }
26
+ case 'binary': {
27
+ const left = evaluateConstantExpression(expression.left, constants);
28
+ const right = evaluateConstantExpression(expression.right, constants);
29
+ if (left === undefined || right === undefined)
30
+ return undefined;
31
+ switch (expression.operator) {
32
+ case '+':
33
+ return left + right;
34
+ case '-':
35
+ return left - right;
36
+ case '*':
37
+ return left * right;
38
+ case '/':
39
+ return right === 0 ? undefined : Math.trunc(left / right);
40
+ case '%':
41
+ return right === 0 ? undefined : left % right;
42
+ case '&':
43
+ return left & right;
44
+ case '^':
45
+ return left ^ right;
46
+ case '|':
47
+ return left | right;
48
+ case '<<':
49
+ return left << right;
50
+ case '>>':
51
+ return left >> right;
52
+ }
53
+ }
54
+ case 'byte-function': {
55
+ const value = evaluateConstantExpression(expression.expression, constants);
56
+ if (value === undefined)
57
+ return undefined;
58
+ return expression.function === 'LSB' ? value & 0xff : (value >> 8) & 0xff;
59
+ }
60
+ default:
61
+ return undefined;
62
+ }
63
+ }
64
+ function collectConstants(items) {
65
+ const constants = new Map();
66
+ let changed = true;
67
+ while (changed) {
68
+ changed = false;
69
+ for (const item of items) {
70
+ if (item.kind !== 'equ' || constants.has(item.name))
71
+ continue;
72
+ const value = evaluateConstantExpression(item.expression, constants);
73
+ if (value === undefined)
74
+ continue;
75
+ constants.set(item.name, value);
76
+ changed = true;
77
+ }
78
+ }
79
+ return constants;
80
+ }
7
81
  function instructionCallTarget(item) {
8
82
  if (item.kind !== 'instruction')
9
83
  return undefined;
@@ -28,13 +102,14 @@ function instructionTailJumpTarget(item, entryNames) {
28
102
  return undefined;
29
103
  return target;
30
104
  }
31
- function toInstruction(item, labels) {
105
+ function toInstruction(item, labels, constants) {
32
106
  return {
33
107
  instruction: item.instruction,
34
108
  file: item.span.sourceName,
35
109
  line: item.span.line,
36
110
  column: item.span.column,
37
111
  labels: [...labels],
112
+ constants,
38
113
  };
39
114
  }
40
115
  function pushDirectBoundary(boundaries, target, subject, file, line, column) {
@@ -43,6 +118,7 @@ function pushDirectBoundary(boundaries, target, subject, file, line, column) {
43
118
  export function buildRegisterCareProgramModel(items) {
44
119
  const routines = [];
45
120
  const directCalls = [];
121
+ const constants = collectConstants(items);
46
122
  const filesWithEntryLabels = new Set(items
47
123
  .filter((item) => item.kind === 'label')
48
124
  .filter((item) => item.isEntry === true)
@@ -73,6 +149,7 @@ export function buildRegisterCareProgramModel(items) {
73
149
  labels: [...labels],
74
150
  entryLabels: [...entryLabels],
75
151
  instructions: [],
152
+ constants,
76
153
  span: {
77
154
  file: sourceName ?? '',
78
155
  start: { line: routineStartLine, column: routineStartColumn ?? 1 },
@@ -89,6 +166,7 @@ export function buildRegisterCareProgramModel(items) {
89
166
  labels: [...labels],
90
167
  entryLabels: [...entryLabels],
91
168
  instructions: [...instructions],
169
+ constants,
92
170
  span: {
93
171
  file: sourceName ?? '',
94
172
  start: { line: routineStartLine, column: routineStartColumn ?? 1 },
@@ -121,7 +199,7 @@ export function buildRegisterCareProgramModel(items) {
121
199
  if (item.span.sourceName !== sourceName) {
122
200
  continue;
123
201
  }
124
- instructions.push(toInstruction(item, labels));
202
+ instructions.push(toInstruction(item, labels, constants));
125
203
  const directTarget = instructionCallTarget(item);
126
204
  if (directTarget !== undefined) {
127
205
  pushDirectBoundary(directCalls, directTarget, `CALL ${directTarget}`, item.span.sourceName, item.span.line, item.span.column);
@@ -24,7 +24,13 @@ export function buildProfileSummaries(profileName) {
24
24
  if (profile === undefined) {
25
25
  return [];
26
26
  }
27
- return [...profile.rst.values(), ...profile.rstServices.values()];
27
+ return [
28
+ ...profile.rst.values(),
29
+ ...profile.rstServices.values(),
30
+ ...[...profile.rstDispatchers.values()].flatMap((dispatcher) => [
31
+ ...dispatcher.services.values(),
32
+ ]),
33
+ ];
28
34
  }
29
35
  export function buildProfileSummaryLookup(profileName) {
30
36
  const profile = getRegisterCareProfile(profileName);
@@ -37,6 +43,11 @@ export function buildProfileSummaryLookup(profileName) {
37
43
  for (const summary of profile.rstServices.values()) {
38
44
  out.set(summary.name, summary);
39
45
  }
46
+ for (const dispatcher of profile.rstDispatchers.values()) {
47
+ for (const summary of dispatcher.services.values()) {
48
+ out.set(summary.name, summary);
49
+ }
50
+ }
40
51
  return out;
41
52
  }
42
53
  export function routineNames(routines) {
@@ -44,7 +55,9 @@ export function routineNames(routines) {
44
55
  }
45
56
  export function buildSummaries(routines, contractMap, profileSummaries = []) {
46
57
  const names = routineNameSet(routines);
47
- const routineSummaries = inferRoutineSummariesToFixedPoint([...routines], contractMap, names, [...profileSummaries]);
58
+ const routineSummaries = inferRoutineSummariesToFixedPoint([...routines], contractMap, names, [
59
+ ...profileSummaries,
60
+ ]);
48
61
  const summaries = routineSummaries.map((item) => item.summary);
49
62
  return summariesWithExternalContracts(summaries, contractMap, names);
50
63
  }
@@ -44,12 +44,14 @@ export interface RegisterCareInstruction {
44
44
  line: number;
45
45
  column: number;
46
46
  labels: string[];
47
+ constants?: ReadonlyMap<string, number>;
47
48
  }
48
49
  export interface RegisterCareRoutine {
49
50
  name: string;
50
51
  labels: string[];
51
52
  entryLabels: string[];
52
53
  instructions: RegisterCareInstruction[];
54
+ constants?: ReadonlyMap<string, number>;
53
55
  span: {
54
56
  file: string;
55
57
  start: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhlagado/azm",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "AZM assembler for the Z80 family (Node.js CLI)",
5
5
  "license": "GPL-3.0-only",
6
6
  "engines": {