From 83003f8a0169637668a58ad6c6a60f4577640f80 Mon Sep 17 00:00:00 2001 From: Neil Webber Date: Tue, 19 Sep 2023 11:58:24 -0500 Subject: [PATCH] better forward referencing --- pdpasmhelper.py | 136 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 17 deletions(-) diff --git a/pdpasmhelper.py b/pdpasmhelper.py index 9e8cdd8..91ebf6c 100644 --- a/pdpasmhelper.py +++ b/pdpasmhelper.py @@ -31,6 +31,7 @@ from contextlib import AbstractContextManager from branches import BRANCH_CODES from collections import namedtuple +from functools import partial FwdRef = namedtuple('FwdRef', ['f', 'loc', 'name', 'block']) @@ -89,7 +90,6 @@ class PDP11InstructionAssembler: An integer, i, can be passed in directly; it is becomes f"${i}." """ - # NOTE: Not all forms implemented yet. See FUNCTIONALITY DISCLAIMER. # for convenience def valerr(): @@ -285,12 +285,19 @@ class PDP11InstructionAssembler: # class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): + FWDREF = object() # this is a sentinel + def __init__(self): super().__init__() self._instblock = [] self._labels = {} self._label_fwdrefs = {} + def operand_parser(self, operand_string, /): + if operand_string is self.FWDREF: + operand_string = 0 + return super().operand_parser(operand_string) + def _seqwords(self, seq): """seq can be an iterable, or a naked (integer) instruction.""" try: @@ -303,10 +310,60 @@ class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): """Returns the length of the sequence in WORDS""" return len(self._instblock) - def label(self, name): - """Record the current position as 'name'.""" - curloc = len(self) - self._labels[name] = curloc + def __dotandnumbers(self, w): + """Turn '.' into 2x current offset, turn numbers into integers""" + if isinstance(w, int): # already an integer + return w + elif w in '+-': + return w + elif w == '.': + return len(self) * 2 + elif w[-1] == '.': # 12345. for example + return int(w[:-1]) + else: + try: + return self.getlabel(w) + except KeyError: + pass + return int(w, 8) + + @staticmethod + def _fwdword(loco, fref): + block = fref.block + fwdoffs = block.getlabel(fref.name) - (2*fref.loc) + block._instblock[fref.loc + loco] = fwdoffs + + def label(self, name, *, value=None): + """Record the current position, or 'value', as 'name'. + + If value is '.', the current position is multiplied by 2 + (so that value will be suitable for adding to a base address) + + A trivial amount of arithmetic processing is allowed in the value: + token + token + token - token + where 'token' is a symbol name, a number, or '.' + """ + + if value is None: + value = '.' + + try: + value_tokens = value.split() + except AttributeError: + value_tokens = [value] + + value_tokens = [self.__dotandnumbers(w) for w in value_tokens] + if len(value_tokens) == 3: + if value_tokens[1] == '+': + value_tokens = [value_tokens[0] + value_tokens[2]] + elif value_tokens[1] == '-': + value_tokens = [value_tokens[0] - value_tokens[2]] + + if len(value_tokens) != 1: + raise ValueError(f"cannot parse '{value}'") + + self._labels[name] = value_tokens[0] try: frefs = self._label_fwdrefs[name] @@ -317,10 +374,11 @@ class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): fref.f(fref) del self._label_fwdrefs[name] - return curloc + return self._labels[name] def getlabel(self, name, *, callback=None): """Return value (loc) of name; register a callback if fwd ref.""" + try: return self._labels[name] except KeyError: @@ -329,12 +387,42 @@ class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): raise # otherwise, register the callback and return None. # Callers prepared for forward references MUST check for None + if callback == "OP1": + callback = self._fwdword_op1 fref = FwdRef(f=callback, loc=len(self), name=name, block=self) try: self._label_fwdrefs[name].append(fref) except KeyError: self._label_fwdrefs[name] = [fref] - return None + return self.FWDREF + + def fwdvalue(self, name, relpos=0): + """Get a forward reference value in a form that can be patched + when the value becomes known. If the value being patched is + the first operand in a 1 or 2 operand instruction, use like this: + For example: + a.mov(a.fwdvalue('somelabel', 'OP1') + + If it is the second operand, use OP2. + """ + + # In the common case where a complete 16-bit word in the + # instruction stream needs patching, specify callback='OP1' + # (the string "OP1") to patch the word that would be considered + # to be the first operand of the instruction. For example: + # a.mov(a.getlabel('somelabel', callback='OP1'), 'r0') + # puts the forward reference value of 'somelabel' into r0. + try: + relpos = relpos.upper() + except AttributeError: + pass + if relpos == '.': + relpos = 0 + elif relpos == 'OP1' or relpos == 'OPERAND_WORD_1': + relpos = 1 + elif relpos == 'OP2' or relpos == 'OPERAND_WORD_2': + relpos = 2 + return self.getlabel(name, callback=partial(self._fwdword, relpos)) @staticmethod def _neg16(x): @@ -356,16 +444,16 @@ class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): # If it's a str, treat it as a (possibly-forward-ref) label if isinstance(x, str): offs = self.getlabel(x, callback=callback) - if offs is not None: + if offs is not self.FWDREF: # got a value - compute the delta - x = offs - (len(self) + 1) + x = offs - (2 * (len(self) + 1)) else: return 0 # fref will be patched later return self._neg16(x) def _branchpatch(self, fref): - fwdoffs = self.getlabel(fref.name) - (fref.loc + 1) + fwdoffs = self.getlabel(fref.name) - (2 * (fref.loc + 1)) block = fref.block block._instblock[fref.loc] |= block.bxx_offset(fwdoffs) @@ -383,11 +471,11 @@ class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): except ValueError: raise ValueError(f"branch target ({target}) too far or illegal") - # offsets come back from _label.. in 16-bit form, convert to 8 - # and make sure not too big in either direction - if offs > 127 and offs < (65536 - 128): + # offsets come back from _label.. in 16-bit form, as byte offsets + # convert to 8 bit and word offset, and make sure not too big + if offs > 254 and offs < (65536 - 256): raise ValueError(f"branch target ('{target}') too far.") - + offs >>= 1 return offs & 0o377 def bne(self, target): @@ -415,11 +503,12 @@ class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): offs = self._label_or_offset(target) - # offsets are always negative and are allowed from 0 to -63 + # offsets are always negative and are allowed from 0 to -126 # but they come from _label... as two's complement, so: - if offs < 0o177701: # (65536-63) + if offs < 0o177602: # (65536-126) raise ValueError(f"sob illegal target {target}") - return self.literal(0o077000 | (reg << 6) | ((-offs) & 0o77)) + + return self.literal(0o077000 | (reg << 6) | (((-offs) >> 1) & 0o77)) def instructions(self): return self._instblock @@ -474,6 +563,19 @@ if __name__ == "__main__": with self.assertRaises(ValueError): a.bne('foo') + def test_labelmath_plus(self): + with ASM() as a: + a.label('L1', value=17) + a.label('L2', value='L1 + 25.') + self.assertEqual(a.getlabel('L2'), 42) + + def test_labelmath_minus(self): + with ASM() as a: + a.label('L1') + a.clr('r0') + a.label('L2', value='. - L1') + self.assertEqual(a.getlabel('L2'), 2) + def test_sob(self): for i in range(63): # 0..62 because the sob also counts with self.subTest(i=i):