dasmos 4.2.0__tar.gz → 4.2.1__tar.gz
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.
- {dasmos-4.2.0/src/dasmos.egg-info → dasmos-4.2.1}/PKG-INFO +2 -2
- {dasmos-4.2.0 → dasmos-4.2.1}/README.md +1 -1
- {dasmos-4.2.0 → dasmos-4.2.1}/pyproject.toml +1 -1
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/__init__.py +1 -1
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/asm_renderer.py +261 -2
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/renderers/beebasm/renderer.py +6 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/renderers/tass64/renderer.py +14 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/renderer.py +23 -0
- {dasmos-4.2.0 → dasmos-4.2.1/src/dasmos.egg-info}/PKG-INFO +2 -2
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos.egg-info/SOURCES.txt +3 -0
- dasmos-4.2.1/tests/test_absolute_operands.py +211 -0
- dasmos-4.2.1/tests/test_expression_symbols.py +162 -0
- dasmos-4.2.1/tests/test_forward_operand_size.py +237 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/LICENSE +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/setup.cfg +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/_text.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/cli.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/annotations.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/classification.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/config.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/cpu_state.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/data_type.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/disassembly.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/expr.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/expr_parse.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/file_header.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/format_hint.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/labels.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/markdown_asm.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/memory.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/move.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/program.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/core/regions.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/cpu.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/disassembler.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/exceptions.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/expr.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/cpus/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/cpus/cpu6502/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/cpus/cpu6502/cpu.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/cpus/cpu65c02/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/cpus/cpu65c02/cpu.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/_acorn_bbc_common.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_fdc_1770/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_fdc_1770/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_fdc_8271/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_fdc_8271/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_master_hardware/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_master_hardware/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_model_b_hardware/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_model_b_hardware/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_mos/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_mos/enums.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_mos/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_mos/hooks.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_sideways_rom/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/acorn_sideways_rom/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/bbc_basic_6502/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/bbc_basic_6502/environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/environments/bbc_basic_6502/floats.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/renderers/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/renderers/beebasm/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/renderers/json/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/renderers/json/renderer.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ext/renderers/tass64/__init__.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/extension.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/hooks.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/ir.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/output.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos/py.typed +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos.egg-info/dependency_links.txt +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos.egg-info/entry_points.txt +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos.egg-info/requires.txt +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/src/dasmos.egg-info/top_level.txt +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_adfs_roundtrip.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_anfs_418_roundtrip.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_anfs_421_roundtrip.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_beebasm.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_cli.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_cpu.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_cpu6502.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_cpu65c02.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_disassembler.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_driver_roundtrip.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_econet_bridge_roundtrip.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_environment.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_expr.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_expr_operators.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_expr_parse.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_file_header.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_golden_asm.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_golden_json.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_include_binary.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_index_regions.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_json_renderer.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_macros.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_mnemonic_hash.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_nfs_334_roundtrip.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_nfs_roundtrip.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_output.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_program.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_py8dis2dasmos.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_reference_kinds.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_renderer.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_smoke.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_tass64.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_trace.py +0 -0
- {dasmos-4.2.0 → dasmos-4.2.1}/tests/test_tube_client_roundtrip.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dasmos
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.1
|
|
4
4
|
Summary: Extensible tracing disassembler for classic CPUs, with CPU, renderer and environment extension points.
|
|
5
5
|
Author-email: Robert Smallshire <rob@sixty-north.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -32,7 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
<img src="https://raw.githubusercontent.com/acornaeology/dasmos/master/docs/_static/dasmos-banner.svg" alt="Dasmos — an extensible tracing disassembler for classic CPUs">
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
|
-
An extensible tracing disassembler for classic CPUs, version `4.2.
|
|
35
|
+
An extensible tracing disassembler for classic CPUs, version `4.2.1`.
|
|
36
36
|
|
|
37
37
|
<p align="center">
|
|
38
38
|
<a href="https://pypi.org/project/dasmos/"><img src="https://img.shields.io/pypi/v/dasmos.svg" alt="PyPI"></a>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<img src="https://raw.githubusercontent.com/acornaeology/dasmos/master/docs/_static/dasmos-banner.svg" alt="Dasmos — an extensible tracing disassembler for classic CPUs">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
An extensible tracing disassembler for classic CPUs, version `4.2.
|
|
5
|
+
An extensible tracing disassembler for classic CPUs, version `4.2.1`.
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://pypi.org/project/dasmos/"><img src="https://img.shields.io/pypi/v/dasmos.svg" alt="PyPI"></a>
|
|
@@ -4,7 +4,7 @@ The top-level package re-exports the public API. Consumers should import
|
|
|
4
4
|
from :mod:`dasmos` rather than reaching into sub-modules.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "4.2.
|
|
7
|
+
__version__ = "4.2.1"
|
|
8
8
|
|
|
9
9
|
from dasmos.core.annotations import Align, Annotation, AnnotationStore, Banner, Comment
|
|
10
10
|
from dasmos.core.format_hint import FormatHint
|
|
@@ -77,6 +77,21 @@ BYTE_COLUMN_TOTAL_WIDTH = 27
|
|
|
77
77
|
# surrounding text.
|
|
78
78
|
BANNER_SEPARATOR_WIDTH = 87
|
|
79
79
|
|
|
80
|
+
# Absolute addressing modes and the zero-page mode a size-optimising
|
|
81
|
+
# assembler (beebasm, 64tass) substitutes when the operand value is
|
|
82
|
+
# below ``&100`` *and* the mnemonic has that zero-page form. An
|
|
83
|
+
# instruction encoded in one of these modes with such an operand must
|
|
84
|
+
# be rendered so the assembler keeps the absolute encoding (#46).
|
|
85
|
+
_ZERO_PAGE_FORM_OF_ABSOLUTE_MODE = {
|
|
86
|
+
"ABSOLUTE": "ZERO_PAGE",
|
|
87
|
+
"ABSOLUTE_X": "ZERO_PAGE_X",
|
|
88
|
+
"ABSOLUTE_Y": "ZERO_PAGE_Y",
|
|
89
|
+
}
|
|
90
|
+
_ABSOLUTE_FORM_OF_ZERO_PAGE_MODE = {
|
|
91
|
+
zero_page: absolute
|
|
92
|
+
for absolute, zero_page in _ZERO_PAGE_FORM_OF_ABSOLUTE_MODE.items()
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
if TYPE_CHECKING:
|
|
81
96
|
from dasmos.ir import IntermediateRepresentation
|
|
82
97
|
|
|
@@ -163,6 +178,11 @@ class AssemblerRenderer(TextRenderer):
|
|
|
163
178
|
# so). Drivers wanting per-expression control can fold selected
|
|
164
179
|
# expressions themselves before registering them.
|
|
165
180
|
self.fold_string_ops = fold_string_ops
|
|
181
|
+
# Initialise the per-render state so the public helpers that
|
|
182
|
+
# rely on it (e.g. :meth:`render_expression`) also work on a
|
|
183
|
+
# renderer that hasn't rendered anything yet; :meth:`render`
|
|
184
|
+
# resets it again at the start of every render.
|
|
185
|
+
self._reset_render_state()
|
|
166
186
|
|
|
167
187
|
@property
|
|
168
188
|
def emit_boundary_labels(self) -> bool:
|
|
@@ -259,6 +279,18 @@ class AssemblerRenderer(TextRenderer):
|
|
|
259
279
|
# Macro names already warned about being inline-expanded, so the
|
|
260
280
|
# warning fires once per macro rather than once per call site.
|
|
261
281
|
self._macros_expansion_warned: set[str] = set()
|
|
282
|
+
# ``(operation, addressing-mode name)`` pairs the IR's CPU
|
|
283
|
+
# offers, built lazily on first use (see
|
|
284
|
+
# :meth:`_needs_forced_absolute`).
|
|
285
|
+
self._cpu_operation_modes: frozenset | None = None
|
|
286
|
+
# Explicit label name -> runtime address, so an expression that
|
|
287
|
+
# names a label (a ``Sym``) can mark it used (see
|
|
288
|
+
# :meth:`_note_symbol_use`). Filled in by :meth:`render`.
|
|
289
|
+
self._label_addr_by_name: dict[str, int] = {}
|
|
290
|
+
# While an instruction's operand is being rendered: the runtime
|
|
291
|
+
# addresses of the labels it names (see :meth:`_render_opcode`);
|
|
292
|
+
# ``None`` otherwise.
|
|
293
|
+
self._operand_label_refs: set[int] | None = None
|
|
262
294
|
|
|
263
295
|
def render(self, ir: "IntermediateRepresentation") -> TextOutput:
|
|
264
296
|
"""Walk the IR's classifications and emit a beebasm source
|
|
@@ -304,6 +336,7 @@ class AssemblerRenderer(TextRenderer):
|
|
|
304
336
|
"""
|
|
305
337
|
self._reset_render_state()
|
|
306
338
|
self._comment_label_resolver = self._build_comment_label_resolver(ir)
|
|
339
|
+
self._label_addr_by_name = self._build_label_addr_by_name(ir)
|
|
307
340
|
|
|
308
341
|
lines: list[str] = []
|
|
309
342
|
lines.extend(self.disassembly_start())
|
|
@@ -859,6 +892,34 @@ class AssemblerRenderer(TextRenderer):
|
|
|
859
892
|
name_to_addr.setdefault(name, addr)
|
|
860
893
|
return name_to_addr.get
|
|
861
894
|
|
|
895
|
+
def _build_label_addr_by_name(self, ir) -> dict[str, int]:
|
|
896
|
+
"""Map each explicit label name to its runtime address (first
|
|
897
|
+
registration wins), for :meth:`_note_symbol_use`."""
|
|
898
|
+
addr_by_name: dict[str, int] = {}
|
|
899
|
+
for runtime_addr_obj, label in ir.labels.items():
|
|
900
|
+
for name in label.explicit_names_in_insertion_order():
|
|
901
|
+
addr_by_name.setdefault(name, int(runtime_addr_obj))
|
|
902
|
+
return addr_by_name
|
|
903
|
+
|
|
904
|
+
def _note_symbol_use(self, ir, name: str) -> None:
|
|
905
|
+
"""Record that an emitted expression names the label ``name``.
|
|
906
|
+
|
|
907
|
+
An expression can refer to a label by *name* (a ``Sym`` — e.g.
|
|
908
|
+
the ``fscv+1`` alias the ``acorn_mos`` environment registers for
|
|
909
|
+
``&021F``, or a driver's ``d.expr(addr, "ptr+1")``) rather than by
|
|
910
|
+
address. That is still a use of the label: an optional label
|
|
911
|
+
outside the loaded range must then get its ``name = &xxxx``
|
|
912
|
+
definition, exactly as an operand that resolves to it by address
|
|
913
|
+
does (#47). A name dasmos doesn't know is left to the driver.
|
|
914
|
+
"""
|
|
915
|
+
addr = self._label_addr_by_name.get(name)
|
|
916
|
+
if addr is None:
|
|
917
|
+
return
|
|
918
|
+
if not self._label_address_is_in_range(ir, addr):
|
|
919
|
+
self._used_external_labels.add(addr)
|
|
920
|
+
if self._operand_label_refs is not None:
|
|
921
|
+
self._operand_label_refs.add(addr)
|
|
922
|
+
|
|
862
923
|
def _build_explicit_label_table(self, ir) -> list[str]:
|
|
863
924
|
"""Return ``name = &xxxx`` definition lines for out-of-range
|
|
864
925
|
labels:
|
|
@@ -1488,15 +1549,210 @@ class AssemblerRenderer(TextRenderer):
|
|
|
1488
1549
|
self, ir, binary_addr, opcode: Opcode, *, active_move=None,
|
|
1489
1550
|
) -> str:
|
|
1490
1551
|
"""Format a single instruction line."""
|
|
1552
|
+
if self._needs_forced_absolute(ir, binary_addr, opcode):
|
|
1553
|
+
return self._render_pinned_size(
|
|
1554
|
+
ir, binary_addr, opcode, absolute=True, active_move=active_move,
|
|
1555
|
+
)
|
|
1491
1556
|
mnemonic = self._apply_case(opcode.default_mnemonic())
|
|
1492
|
-
operand
|
|
1493
|
-
|
|
1557
|
+
# Collect the labels the operand names, to spot a forward
|
|
1558
|
+
# reference whose size the assembler would have to guess.
|
|
1559
|
+
self._operand_label_refs = set()
|
|
1560
|
+
try:
|
|
1561
|
+
operand = self._render_operand(
|
|
1562
|
+
ir, binary_addr, opcode, active_move=active_move,
|
|
1563
|
+
)
|
|
1564
|
+
referenced = self._operand_label_refs
|
|
1565
|
+
finally:
|
|
1566
|
+
self._operand_label_refs = None
|
|
1567
|
+
pin_absolute = self._forward_reference_pin(
|
|
1568
|
+
ir, binary_addr, opcode, referenced, active_move=active_move,
|
|
1494
1569
|
)
|
|
1570
|
+
if pin_absolute is not None:
|
|
1571
|
+
return self._render_pinned_size(
|
|
1572
|
+
ir, binary_addr, opcode,
|
|
1573
|
+
absolute=pin_absolute, active_move=active_move,
|
|
1574
|
+
)
|
|
1495
1575
|
char_hint = self._immediate_char_hint(ir, binary_addr, opcode)
|
|
1496
1576
|
if operand:
|
|
1497
1577
|
return f" {mnemonic} {operand}{char_hint}"
|
|
1498
1578
|
return f" {mnemonic}"
|
|
1499
1579
|
|
|
1580
|
+
def _cpu_has_mode(self, ir, operation, mode_name: str) -> bool:
|
|
1581
|
+
"""True iff the IR's CPU offers ``operation`` in the addressing
|
|
1582
|
+
mode named ``mode_name``."""
|
|
1583
|
+
if self._cpu_operation_modes is None:
|
|
1584
|
+
self._cpu_operation_modes = frozenset(
|
|
1585
|
+
(op.operation, op.addressing_mode.name)
|
|
1586
|
+
for op in ir.cpu.opcodes().values()
|
|
1587
|
+
)
|
|
1588
|
+
return (operation, mode_name) in self._cpu_operation_modes
|
|
1589
|
+
|
|
1590
|
+
def _needs_forced_absolute(self, ir, binary_addr, opcode: Opcode) -> bool:
|
|
1591
|
+
"""True when the instruction is encoded with absolute addressing
|
|
1592
|
+
but a size-optimising assembler would reassemble it to the
|
|
1593
|
+
shorter zero-page form.
|
|
1594
|
+
|
|
1595
|
+
That happens exactly when the operand value is below ``&100``
|
|
1596
|
+
*and* the mnemonic has the matching zero-page mode — the rule
|
|
1597
|
+
beebasm and 64tass both apply. ``LDA abs,Y`` with a zero-page
|
|
1598
|
+
operand is therefore safe (LDA has no ``zp,Y``), while
|
|
1599
|
+
``LDX abs,Y`` is not (#46).
|
|
1600
|
+
"""
|
|
1601
|
+
zero_page_mode = _ZERO_PAGE_FORM_OF_ABSOLUTE_MODE.get(
|
|
1602
|
+
opcode.addressing_mode.name,
|
|
1603
|
+
)
|
|
1604
|
+
if zero_page_mode is None:
|
|
1605
|
+
return False
|
|
1606
|
+
if ir.memory.get_u16_le(int(binary_addr) + 1) >= 0x100:
|
|
1607
|
+
return False
|
|
1608
|
+
return self._cpu_has_mode(ir, opcode.operation, zero_page_mode)
|
|
1609
|
+
|
|
1610
|
+
def _forward_reference_pin(
|
|
1611
|
+
self, ir, binary_addr, opcode: Opcode, referenced, *, active_move=None,
|
|
1612
|
+
) -> bool | None:
|
|
1613
|
+
"""Decide whether an instruction's operand size must be pinned
|
|
1614
|
+
because its operand forward-references a label (#48).
|
|
1615
|
+
|
|
1616
|
+
When a mnemonic has both a zero-page and an absolute form, the
|
|
1617
|
+
assembler can only choose between them once it knows the
|
|
1618
|
+
operand's value. For a label defined later in the listing it
|
|
1619
|
+
must guess on its first pass, and a wrong guess can settle into
|
|
1620
|
+
a self-consistent but wrong layout: the guessed size moves the
|
|
1621
|
+
label to the matching side of ``&100``. So the size is pinned
|
|
1622
|
+
when the operand names a label defined later *and* this
|
|
1623
|
+
assembler's first-pass guess
|
|
1624
|
+
(:meth:`forward_reference_assumes_zero_page`) disagrees with
|
|
1625
|
+
the encoded size. Where the guess already matches, the
|
|
1626
|
+
instruction renders normally.
|
|
1627
|
+
|
|
1628
|
+
Returns ``True`` to pin the absolute form, ``False`` to pin the
|
|
1629
|
+
zero-page form, or ``None`` when no pinning is needed.
|
|
1630
|
+
"""
|
|
1631
|
+
mode_name = opcode.addressing_mode.name
|
|
1632
|
+
if mode_name in _ZERO_PAGE_FORM_OF_ABSOLUTE_MODE:
|
|
1633
|
+
encoded_zero_page = False
|
|
1634
|
+
alternative = _ZERO_PAGE_FORM_OF_ABSOLUTE_MODE[mode_name]
|
|
1635
|
+
elif mode_name in _ABSOLUTE_FORM_OF_ZERO_PAGE_MODE:
|
|
1636
|
+
encoded_zero_page = True
|
|
1637
|
+
alternative = _ABSOLUTE_FORM_OF_ZERO_PAGE_MODE[mode_name]
|
|
1638
|
+
else:
|
|
1639
|
+
return None
|
|
1640
|
+
if not self._cpu_has_mode(ir, opcode.operation, alternative):
|
|
1641
|
+
return None
|
|
1642
|
+
if not any(self._label_defined_later(ir, addr) for addr in referenced):
|
|
1643
|
+
return None
|
|
1644
|
+
if active_move is not None:
|
|
1645
|
+
pc = int(active_move.dest_runtime_addr) + (
|
|
1646
|
+
int(binary_addr) - int(active_move.src_binary_addr)
|
|
1647
|
+
)
|
|
1648
|
+
else:
|
|
1649
|
+
pc = int(ir.moves.b2r(BinaryAddr(int(binary_addr))))
|
|
1650
|
+
if self.forward_reference_assumes_zero_page(pc) == encoded_zero_page:
|
|
1651
|
+
return None
|
|
1652
|
+
return not encoded_zero_page
|
|
1653
|
+
|
|
1654
|
+
def _label_defined_later(self, ir, runtime_addr: int) -> bool:
|
|
1655
|
+
"""True iff the label at ``runtime_addr`` is defined further on
|
|
1656
|
+
in the listing than the line being rendered.
|
|
1657
|
+
|
|
1658
|
+
Labels outside the loaded range are equates at the top of the
|
|
1659
|
+
listing, and in-range labels with no inline anchor go into the
|
|
1660
|
+
same table, so only an inline anchor (or an inline
|
|
1661
|
+
``name = base+offset``) not yet emitted is still to come.
|
|
1662
|
+
"""
|
|
1663
|
+
if runtime_addr in self._inline_emitted_runtime_addrs:
|
|
1664
|
+
return False
|
|
1665
|
+
if not self._label_address_is_in_range(ir, runtime_addr):
|
|
1666
|
+
return False
|
|
1667
|
+
return (
|
|
1668
|
+
self._label_address_is_classification_start(ir, runtime_addr)
|
|
1669
|
+
or self._label_has_inline_offset_base(ir, runtime_addr)
|
|
1670
|
+
)
|
|
1671
|
+
|
|
1672
|
+
def forward_reference_assumes_zero_page(self, pc: int) -> bool:
|
|
1673
|
+
"""Whether this assembler's first pass assumes zero-page
|
|
1674
|
+
addressing for an instruction at ``pc`` whose operand is a
|
|
1675
|
+
not-yet-defined symbol.
|
|
1676
|
+
|
|
1677
|
+
Default ``False``: an undefined symbol is assumed to be 16-bit,
|
|
1678
|
+
so the absolute form is chosen (64tass's behaviour). Backends
|
|
1679
|
+
whose assembler guesses otherwise override this; beebasm
|
|
1680
|
+
substitutes the instruction's own address, so it guesses zero
|
|
1681
|
+
page exactly when ``pc`` is below ``&100``.
|
|
1682
|
+
"""
|
|
1683
|
+
return False
|
|
1684
|
+
|
|
1685
|
+
def _render_pinned_size(
|
|
1686
|
+
self, ir, binary_addr, opcode: Opcode, *, absolute: bool,
|
|
1687
|
+
active_move=None,
|
|
1688
|
+
) -> str:
|
|
1689
|
+
"""Render an instruction so it reassembles to its encoded size —
|
|
1690
|
+
absolute (three bytes) or zero page (two bytes) — whatever the
|
|
1691
|
+
assembler would otherwise choose.
|
|
1692
|
+
|
|
1693
|
+
The backend's :meth:`force_abs_instruction` /
|
|
1694
|
+
:meth:`force_zp_instruction` supply its operand-size syntax
|
|
1695
|
+
where it has one (64tass ``@w`` / ``@b``). Otherwise the
|
|
1696
|
+
instruction is emitted as data via
|
|
1697
|
+
:meth:`forced_absolute_as_data` /
|
|
1698
|
+
:meth:`forced_zero_page_as_data`, with the instruction kept
|
|
1699
|
+
readable in a trailing comment.
|
|
1700
|
+
"""
|
|
1701
|
+
mnemonic = self._apply_case(opcode.default_mnemonic())
|
|
1702
|
+
symbol = self._resolve_operand_symbol(
|
|
1703
|
+
ir, binary_addr, opcode, int(binary_addr) + 1,
|
|
1704
|
+
active_move=active_move,
|
|
1705
|
+
)
|
|
1706
|
+
mode_name = opcode.addressing_mode.name
|
|
1707
|
+
if mode_name.endswith("_X"):
|
|
1708
|
+
suffix = f",{self._apply_case('X')}"
|
|
1709
|
+
elif mode_name.endswith("_Y"):
|
|
1710
|
+
suffix = f",{self._apply_case('Y')}"
|
|
1711
|
+
else:
|
|
1712
|
+
suffix = ""
|
|
1713
|
+
if absolute:
|
|
1714
|
+
forced = self.force_abs_instruction(mnemonic, "", symbol, suffix)
|
|
1715
|
+
else:
|
|
1716
|
+
forced = self.force_zp_instruction(mnemonic, "", symbol, suffix)
|
|
1717
|
+
if forced is not None:
|
|
1718
|
+
return f" {forced}"
|
|
1719
|
+
opcode_byte = ir.memory.get_u8(binary_addr)
|
|
1720
|
+
if absolute:
|
|
1721
|
+
data = self.forced_absolute_as_data(opcode_byte, symbol)
|
|
1722
|
+
size = "absolute"
|
|
1723
|
+
else:
|
|
1724
|
+
data = self.forced_zero_page_as_data(opcode_byte, symbol)
|
|
1725
|
+
size = "zero page"
|
|
1726
|
+
return (
|
|
1727
|
+
f" {data} {self.comment_prefix()} "
|
|
1728
|
+
f"{mnemonic} {symbol}{suffix} ({size})"
|
|
1729
|
+
)
|
|
1730
|
+
|
|
1731
|
+
def forced_absolute_as_data(self, opcode_byte: int, operand: str) -> str:
|
|
1732
|
+
"""An absolute-mode instruction spelled as data: its opcode byte
|
|
1733
|
+
then its 16-bit operand, as one source line.
|
|
1734
|
+
|
|
1735
|
+
Used when the assembler has no syntax to force absolute
|
|
1736
|
+
addressing (:meth:`force_abs_instruction` returned ``None``).
|
|
1737
|
+
The default joins a byte and a word directive with ``:``, the
|
|
1738
|
+
statement separator beebasm (the backend that needs this)
|
|
1739
|
+
accepts. Override for an assembler that separates statements
|
|
1740
|
+
differently.
|
|
1741
|
+
"""
|
|
1742
|
+
return (
|
|
1743
|
+
f"{self.byte_prefix()}{self.hex2(opcode_byte)} : "
|
|
1744
|
+
f"{self.word_prefix()}{operand}"
|
|
1745
|
+
)
|
|
1746
|
+
|
|
1747
|
+
def forced_zero_page_as_data(self, opcode_byte: int, operand: str) -> str:
|
|
1748
|
+
"""A zero-page instruction spelled as data: its opcode byte then
|
|
1749
|
+
its 8-bit operand, as one byte directive.
|
|
1750
|
+
|
|
1751
|
+
Used when the assembler has no syntax to force zero-page
|
|
1752
|
+
addressing (:meth:`force_zp_instruction` returned ``None``).
|
|
1753
|
+
"""
|
|
1754
|
+
return f"{self.byte_prefix()}{self.hex2(opcode_byte)}, {operand}"
|
|
1755
|
+
|
|
1500
1756
|
def _apply_case(self, text: str) -> str:
|
|
1501
1757
|
"""Apply the renderer's ``lower_case`` setting to ``text``.
|
|
1502
1758
|
|
|
@@ -1834,6 +2090,8 @@ class AssemblerRenderer(TextRenderer):
|
|
|
1834
2090
|
if names:
|
|
1835
2091
|
if not self._label_address_is_in_range(ir, addr):
|
|
1836
2092
|
self._used_external_labels.add(addr)
|
|
2093
|
+
if self._operand_label_refs is not None:
|
|
2094
|
+
self._operand_label_refs.add(addr)
|
|
1837
2095
|
return names[0]
|
|
1838
2096
|
# No explicit name — fall back to an expression. Drivers
|
|
1839
2097
|
# use ``d.expr_label(addr, "<base>-1")`` to register a
|
|
@@ -2190,6 +2448,7 @@ class AssemblerRenderer(TextRenderer):
|
|
|
2190
2448
|
if isinstance(e, Int):
|
|
2191
2449
|
return self._render_int_node(e), self._ATOM_PRECEDENCE
|
|
2192
2450
|
if isinstance(e, Sym):
|
|
2451
|
+
self._note_symbol_use(ir, e.name)
|
|
2193
2452
|
return e.name, self._ATOM_PRECEDENCE
|
|
2194
2453
|
if isinstance(e, Ref):
|
|
2195
2454
|
return (
|
|
@@ -412,6 +412,12 @@ class BeebasmRenderer(AssemblerRenderer):
|
|
|
412
412
|
def render_string_length(self, s: str) -> str:
|
|
413
413
|
return f"LEN({s})"
|
|
414
414
|
|
|
415
|
+
def forward_reference_assumes_zero_page(self, pc: int) -> bool:
|
|
416
|
+
# On its first pass beebasm substitutes the current PC for an
|
|
417
|
+
# undefined symbol (assemble.cpp), so it guesses zero page
|
|
418
|
+
# exactly when the instruction itself sits below &100.
|
|
419
|
+
return pc < 0x100
|
|
420
|
+
|
|
415
421
|
def included_binary_directive(self, path: str) -> list[str]:
|
|
416
422
|
# beebasm includes an external file verbatim at assembly time
|
|
417
423
|
# with ``incbin "<path>"``; the file must sit next to the
|
|
@@ -202,6 +202,20 @@ class Tass64Renderer(AssemblerRenderer):
|
|
|
202
202
|
return chr(n)
|
|
203
203
|
return None
|
|
204
204
|
|
|
205
|
+
def force_abs_instruction(
|
|
206
|
+
self, instruction: str, prefix: str, operand: str, suffix: str,
|
|
207
|
+
) -> str | None:
|
|
208
|
+
# 64tass's ``@w`` operand-size prefix forces the 16-bit
|
|
209
|
+
# (absolute) encoding even when the value fits in zero page.
|
|
210
|
+
return f"{instruction} {prefix}@w {operand}{suffix}"
|
|
211
|
+
|
|
212
|
+
def force_zp_instruction(
|
|
213
|
+
self, instruction: str, prefix: str, operand: str, suffix: str,
|
|
214
|
+
) -> str | None:
|
|
215
|
+
# ``@b`` forces the 8-bit (zero-page) encoding, so a forward
|
|
216
|
+
# reference can't settle on the absolute form (#48).
|
|
217
|
+
return f"{instruction} {prefix}@b {operand}{suffix}"
|
|
218
|
+
|
|
205
219
|
def included_binary_directive(self, path: str) -> list[str]:
|
|
206
220
|
# 64tass includes an external file with ``.binary "<path>"``
|
|
207
221
|
# (raw bytes, no header). The file must sit next to the listing
|
|
@@ -235,11 +235,34 @@ class TextRenderer(Renderer[TextOutput]):
|
|
|
235
235
|
def force_zp_instruction(
|
|
236
236
|
self, instruction: str, prefix: str, operand: str, suffix: str,
|
|
237
237
|
) -> str | None:
|
|
238
|
+
"""Instruction text that forces zero-page (8-bit) addressing, or
|
|
239
|
+
``None`` when the assembler has no syntax for it.
|
|
240
|
+
|
|
241
|
+
Called for a zero-page instruction whose operand names a label
|
|
242
|
+
defined later in the listing, when the assembler's first pass
|
|
243
|
+
would otherwise guess the absolute form (see
|
|
244
|
+
``AssemblerRenderer.forward_reference_assumes_zero_page``).
|
|
245
|
+
Arguments as for :meth:`force_abs_instruction`. 64tass returns
|
|
246
|
+
``lda @b name``; the default ``None`` makes the renderer emit
|
|
247
|
+
the instruction as data instead.
|
|
248
|
+
"""
|
|
238
249
|
return None
|
|
239
250
|
|
|
240
251
|
def force_abs_instruction(
|
|
241
252
|
self, instruction: str, prefix: str, operand: str, suffix: str,
|
|
242
253
|
) -> str | None:
|
|
254
|
+
"""Instruction text that forces absolute (16-bit) addressing, or
|
|
255
|
+
``None`` when the assembler has no syntax for it.
|
|
256
|
+
|
|
257
|
+
Called for an instruction encoded with absolute addressing whose
|
|
258
|
+
operand is below ``&100`` and whose mnemonic also has a
|
|
259
|
+
zero-page form — the case a size-optimising assembler would
|
|
260
|
+
otherwise shrink to two bytes. ``instruction`` is the mnemonic,
|
|
261
|
+
``operand`` the resolved symbol, ``prefix`` / ``suffix`` the
|
|
262
|
+
surrounding mode punctuation (``""`` / ``",x"`` / ``",y"``).
|
|
263
|
+
64tass returns ``sta @w name``; the default ``None`` makes the
|
|
264
|
+
renderer emit the instruction as data instead.
|
|
265
|
+
"""
|
|
243
266
|
return None
|
|
244
267
|
|
|
245
268
|
def force_zp_label_prefix(self) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dasmos
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.1
|
|
4
4
|
Summary: Extensible tracing disassembler for classic CPUs, with CPU, renderer and environment extension points.
|
|
5
5
|
Author-email: Robert Smallshire <rob@sixty-north.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -32,7 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
<img src="https://raw.githubusercontent.com/acornaeology/dasmos/master/docs/_static/dasmos-banner.svg" alt="Dasmos — an extensible tracing disassembler for classic CPUs">
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
|
-
An extensible tracing disassembler for classic CPUs, version `4.2.
|
|
35
|
+
An extensible tracing disassembler for classic CPUs, version `4.2.1`.
|
|
36
36
|
|
|
37
37
|
<p align="center">
|
|
38
38
|
<a href="https://pypi.org/project/dasmos/"><img src="https://img.shields.io/pypi/v/dasmos.svg" alt="PyPI"></a>
|
|
@@ -71,6 +71,7 @@ src/dasmos/ext/renderers/json/__init__.py
|
|
|
71
71
|
src/dasmos/ext/renderers/json/renderer.py
|
|
72
72
|
src/dasmos/ext/renderers/tass64/__init__.py
|
|
73
73
|
src/dasmos/ext/renderers/tass64/renderer.py
|
|
74
|
+
tests/test_absolute_operands.py
|
|
74
75
|
tests/test_adfs_roundtrip.py
|
|
75
76
|
tests/test_anfs_418_roundtrip.py
|
|
76
77
|
tests/test_anfs_421_roundtrip.py
|
|
@@ -86,7 +87,9 @@ tests/test_environment.py
|
|
|
86
87
|
tests/test_expr.py
|
|
87
88
|
tests/test_expr_operators.py
|
|
88
89
|
tests/test_expr_parse.py
|
|
90
|
+
tests/test_expression_symbols.py
|
|
89
91
|
tests/test_file_header.py
|
|
92
|
+
tests/test_forward_operand_size.py
|
|
90
93
|
tests/test_golden_asm.py
|
|
91
94
|
tests/test_golden_json.py
|
|
92
95
|
tests/test_include_binary.py
|