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.impl import * # noqa
from ichor.isa import * # noqa from ichor.isa import * # noqa
from ichor.typing 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.isa import Opcode
from ichor.state import Module, Variant from ichor.state import Module, Variant
from ichor.assembler import FuncBuilder
BOOTSTRAP = Module() 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),
]