Slam out a two-pass assembler
This commit is contained in:
parent
7e0273c7ad
commit
7e5a558163
4 changed files with 111 additions and 0 deletions
|
@ -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 *
|
||||
|
|
62
projects/shoggoth/src/python/ichor/assembler.py
Normal file
62
projects/shoggoth/src/python/ichor/assembler.py
Normal 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
|
|
@ -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()
|
||||
|
|
47
projects/shoggoth/test/python/ichor/test_assembler.py
Normal file
47
projects/shoggoth/test/python/ichor/test_assembler.py
Normal 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),
|
||||
]
|
Loading…
Reference in a new issue