source/projects/shoggoth/src/python/ichor/assembler.py

63 lines
1.7 KiB
Python
Raw Normal View History

2022-07-02 06:35:03 +00:00
#!/usr/bin/env python3
from dataclasses import dataclass
from random import choices
from string import ascii_lowercase, digits
from typing import List
from ichor.isa import Opcode
@dataclass
class Label(object):
name: str
def __hash__(self):
return hash(self.name)
class FuncBuilder(object):
def __init__(self) -> None:
self._opcodes = []
def write(self, op: Opcode):
self._opcodes.append(op)
def make_label(self, prefix=""):
frag = ''.join(choices(ascii_lowercase + digits, k=8))
return Label(f"{prefix or 'gensym'}_{frag}")
def set_label(self, label: Label):
self._opcodes.append(label)
def build(self) -> List[Opcode]:
"""Assemble the written body into fully resolved opcodes."""
# The trivial two-pass assembler. First pass removes labels from the
# opcode stream, marking where they occurred.
labels = {}
unassembled = []
for op in self._opcodes:
match op:
case Label(_) as l:
assert l not in labels # Label marks must be unique.
labels[l] = len(unassembled)
case o:
unassembled.append(o)
# Second pass rewrites instructions (which can reference forwards OR
# backwards labels) with real targets instead of labels.
assembled = []
for op in unassembled:
match op:
case Opcode.GOTO(Label(_) as l):
assembled.append(Opcode.GOTO(labels[l]))
case Opcode.VTEST(Label(_) as l):
assembled.append(Opcode.VTEST(labels[l]))
case o:
assembled.append(o)
return assembled