Slam out a two-pass assembler

This commit is contained in:
Reid 'arrdem' McKenzie 2022-07-02 00:35:03 -06:00
parent 7e0273c7ad
commit 7e5a558163
4 changed files with 111 additions and 0 deletions

View file

@ -10,3 +10,4 @@ from ichor.bootstrap import * # noqa
from ichor.impl import * # noqa
from ichor.isa import * # noqa
from ichor.typing import * # noqa
from ichor.assembler import *

View file

@ -0,0 +1,62 @@
#!/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

View file

@ -6,6 +6,7 @@ Hopefully no "real" interpreter ever uses this code, since it's obviously replac
from ichor.isa import Opcode
from ichor.state import Module, Variant
from ichor.assembler import FuncBuilder
BOOTSTRAP = Module()

View file

@ -0,0 +1,47 @@
#!/usr/bin/env python3
from ichor import FuncBuilder, Opcode
import pytest
@pytest.fixture
def builder() -> FuncBuilder:
return FuncBuilder()
def test_forwards_label(builder: FuncBuilder):
l = builder.make_label()
builder.write(Opcode.GOTO(l))
builder.write(Opcode.DROP(0)) # no-op
builder.set_label(l)
builder.write(Opcode.DROP(0)) # no-op
instrs = builder.build()
assert instrs == [
Opcode.GOTO(2),
Opcode.DROP(0),
Opcode.DROP(0),
]
def test_backwards_label(builder: FuncBuilder):
l = builder.make_label()
builder.set_label(l)
builder.write(Opcode.DROP(0)) # no-op
builder.write(Opcode.GOTO(l))
instrs = builder.build()
assert instrs == [
Opcode.DROP(0),
Opcode.GOTO(0),
]
def test_self_label(builder: FuncBuilder):
l = builder.make_label()
builder.write(Opcode.DROP(0)) # no-op
builder.set_label(l)
builder.write(Opcode.GOTO(l))
instrs = builder.build()
assert instrs == [
Opcode.DROP(0),
Opcode.GOTO(1),
]