146 lines
3.1 KiB
Go
146 lines
3.1 KiB
Go
// Package asm is the bytecode assembler: instructions, deduped constants, labels and the line table.
|
|
package asm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"aego/script/bytecode"
|
|
)
|
|
|
|
type Label int
|
|
|
|
type fixup struct {
|
|
pc int
|
|
label Label
|
|
}
|
|
|
|
type Builder struct {
|
|
code []bytecode.Instr
|
|
consts []bytecode.Const
|
|
index map[bytecode.Const]int
|
|
lines []bytecode.LineEntry
|
|
labels []int
|
|
fixups []fixup
|
|
line int
|
|
maxReg int
|
|
err error
|
|
}
|
|
|
|
func NewBuilder() *Builder {
|
|
return &Builder{index: map[bytecode.Const]int{}}
|
|
}
|
|
|
|
func (b *Builder) fail(format string, args ...any) {
|
|
if b.err == nil {
|
|
b.err = fmt.Errorf(format, args...)
|
|
}
|
|
}
|
|
|
|
func (b *Builder) Use(top int) {
|
|
if top > b.maxReg {
|
|
b.maxReg = top
|
|
}
|
|
}
|
|
|
|
func (b *Builder) MaxReg() int {
|
|
return b.maxReg
|
|
}
|
|
|
|
func (b *Builder) PC() int {
|
|
return len(b.code)
|
|
}
|
|
|
|
func (b *Builder) SetLine(line int) {
|
|
b.line = line
|
|
}
|
|
|
|
func (b *Builder) emit(in bytecode.Instr) int {
|
|
pc := len(b.code)
|
|
if b.line > 0 && (len(b.lines) == 0 || int(b.lines[len(b.lines)-1].Line) != b.line) {
|
|
b.lines = append(b.lines, bytecode.LineEntry{PC: uint32(pc), Line: uint32(b.line)})
|
|
}
|
|
b.code = append(b.code, in)
|
|
return pc
|
|
}
|
|
|
|
func (b *Builder) u8(v int, what string) uint8 {
|
|
if v < 0 || v > 255 {
|
|
b.fail("%s %d does not fit in 8 bits", what, v)
|
|
return 0
|
|
}
|
|
return uint8(v)
|
|
}
|
|
|
|
func (b *Builder) ABC(op bytecode.Opcode, a, bb, c int) {
|
|
b.emit(bytecode.ABC(op, b.u8(a, "register"), b.u8(bb, "operand"), b.u8(c, "operand")))
|
|
}
|
|
|
|
func (b *Builder) ABx(op bytecode.Opcode, a, bx int) {
|
|
if bx < 0 || bx > 65535 {
|
|
b.fail("operand %d does not fit in 16 bits", bx)
|
|
bx = 0
|
|
}
|
|
b.emit(bytecode.ABx(op, b.u8(a, "register"), uint16(bx)))
|
|
}
|
|
|
|
func (b *Builder) AsBx(op bytecode.Opcode, a, sbx int) {
|
|
if sbx < -32768 || sbx > 32767 {
|
|
b.fail("operand %d does not fit in 16 bits", sbx)
|
|
sbx = 0
|
|
}
|
|
b.emit(bytecode.AsBx(op, b.u8(a, "register"), int16(sbx)))
|
|
}
|
|
|
|
func (b *Builder) Const(k bytecode.Const) int {
|
|
if i, ok := b.index[k]; ok {
|
|
return i
|
|
}
|
|
i := len(b.consts)
|
|
b.consts = append(b.consts, k)
|
|
b.index[k] = i
|
|
return i
|
|
}
|
|
|
|
func (b *Builder) NewLabel() Label {
|
|
b.labels = append(b.labels, -1)
|
|
return Label(len(b.labels) - 1)
|
|
}
|
|
|
|
func (b *Builder) Mark(l Label) {
|
|
if b.labels[l] >= 0 {
|
|
b.fail("label %d is placed twice", l)
|
|
return
|
|
}
|
|
b.labels[l] = len(b.code)
|
|
}
|
|
|
|
func (b *Builder) Jump(op bytecode.Opcode, a int, l Label) {
|
|
pc := b.emit(bytecode.AsBx(op, b.u8(a, "register"), 0))
|
|
b.fixups = append(b.fixups, fixup{pc: pc, label: l})
|
|
}
|
|
|
|
func (b *Builder) Finish() ([]bytecode.Instr, []bytecode.Const, []bytecode.LineEntry, error) {
|
|
for _, f := range b.fixups {
|
|
target := b.labels[f.label]
|
|
if target < 0 {
|
|
b.fail("label %d is never placed", f.label)
|
|
continue
|
|
}
|
|
if target >= len(b.code) {
|
|
b.fail("label %d points past the end of the function; emit RET before Finish", f.label)
|
|
continue
|
|
}
|
|
off := target - (f.pc + 1)
|
|
if off < -32768 || off > 32767 {
|
|
b.fail("jump of %d instructions is too far", off)
|
|
continue
|
|
}
|
|
in := b.code[f.pc]
|
|
b.code[f.pc] = bytecode.AsBx(in.Op(), in.A(), int16(off))
|
|
}
|
|
if b.err != nil {
|
|
return nil, nil, nil, b.err
|
|
}
|
|
return b.code, b.consts, b.lines, nil
|
|
}
|