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