mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
65199a2274
A recorded macro is prefixed with an internal macro op that stores
the end of the macro. The end is used to efficiently skip the macro
when not calling it. The call a macro, the CallOp stores the start
position of the macro.
To support seamless wrapping of Ops lists, this change removes the
dependency on the macro op prefix from CallOp. Internal code can
now call an Ops like this:
var ops op.Ops
var wrapper op.Ops
ops.AddCall(&wrapper.Internal, &ops.Internal, ops.PC{}, ops.PCFor(&ops.Internal))
References: https://todo.sr.ht/~eliasnaur/gio/318
Signed-off-by: Elias Naur <mail@eliasnaur.com>
196 lines
3.6 KiB
Go
196 lines
3.6 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package ops
|
|
|
|
import (
|
|
"encoding/binary"
|
|
)
|
|
|
|
// Reader parses an ops list.
|
|
type Reader struct {
|
|
pc PC
|
|
stack []macro
|
|
ops *Ops
|
|
deferOps Ops
|
|
deferDone bool
|
|
}
|
|
|
|
// EncodedOp represents an encoded op returned by
|
|
// Reader.
|
|
type EncodedOp struct {
|
|
Key Key
|
|
Data []byte
|
|
Refs []interface{}
|
|
}
|
|
|
|
// Key is a unique key for a given op.
|
|
type Key struct {
|
|
ops *Ops
|
|
pc int
|
|
version int
|
|
}
|
|
|
|
// Shadow of op.MacroOp.
|
|
type macroOp struct {
|
|
ops *Ops
|
|
start PC
|
|
end PC
|
|
}
|
|
|
|
// PC is an instruction counter for an operation list.
|
|
type PC struct {
|
|
data int
|
|
refs int
|
|
}
|
|
|
|
type macro struct {
|
|
ops *Ops
|
|
retPC PC
|
|
endPC PC
|
|
}
|
|
|
|
type opMacroDef struct {
|
|
endpc PC
|
|
}
|
|
|
|
func (pc PC) Add(op OpType) PC {
|
|
return PC{
|
|
data: pc.data + op.Size(),
|
|
refs: pc.refs + op.NumRefs(),
|
|
}
|
|
}
|
|
|
|
// Reset start reading from the beginning of ops.
|
|
func (r *Reader) Reset(ops *Ops) {
|
|
r.ResetAt(ops, PC{})
|
|
}
|
|
|
|
// ResetAt is like Reset, except it starts reading from pc.
|
|
func (r *Reader) ResetAt(ops *Ops, pc PC) {
|
|
r.stack = r.stack[:0]
|
|
Reset(&r.deferOps)
|
|
r.deferDone = false
|
|
r.pc = pc
|
|
r.ops = ops
|
|
}
|
|
|
|
func (r *Reader) Decode() (EncodedOp, bool) {
|
|
if r.ops == nil {
|
|
return EncodedOp{}, false
|
|
}
|
|
deferring := false
|
|
for {
|
|
if len(r.stack) > 0 {
|
|
b := r.stack[len(r.stack)-1]
|
|
if r.pc == b.endPC {
|
|
r.ops = b.ops
|
|
r.pc = b.retPC
|
|
r.stack = r.stack[:len(r.stack)-1]
|
|
continue
|
|
}
|
|
}
|
|
data := r.ops.data
|
|
data = data[r.pc.data:]
|
|
refs := r.ops.refs
|
|
if len(data) == 0 {
|
|
if r.deferDone {
|
|
return EncodedOp{}, false
|
|
}
|
|
r.deferDone = true
|
|
// Execute deferred macros.
|
|
r.ops = &r.deferOps
|
|
r.pc = PC{}
|
|
continue
|
|
}
|
|
key := Key{ops: r.ops, pc: r.pc.data, version: r.ops.version}
|
|
t := OpType(data[0])
|
|
n := t.Size()
|
|
nrefs := t.NumRefs()
|
|
data = data[:n]
|
|
refs = refs[r.pc.refs:]
|
|
refs = refs[:nrefs]
|
|
switch t {
|
|
case TypeDefer:
|
|
deferring = true
|
|
r.pc.data += n
|
|
r.pc.refs += nrefs
|
|
continue
|
|
case TypeAux:
|
|
// An Aux operations is always wrapped in a macro, and
|
|
// its length is the remaining space.
|
|
block := r.stack[len(r.stack)-1]
|
|
n += block.endPC.data - r.pc.data - TypeAuxLen
|
|
data = data[:n]
|
|
case TypeCall:
|
|
if deferring {
|
|
deferring = false
|
|
// Copy macro for deferred execution.
|
|
if t.NumRefs() != 1 {
|
|
panic("internal error: unexpected number of macro refs")
|
|
}
|
|
deferData := Write1(&r.deferOps, t.Size(), refs[0])
|
|
copy(deferData, data)
|
|
r.pc.data += n
|
|
r.pc.refs += nrefs
|
|
continue
|
|
}
|
|
var op macroOp
|
|
op.decode(data, refs)
|
|
retPC := r.pc
|
|
retPC.data += n
|
|
retPC.refs += nrefs
|
|
r.stack = append(r.stack, macro{
|
|
ops: r.ops,
|
|
retPC: retPC,
|
|
endPC: op.end,
|
|
})
|
|
r.ops = op.ops
|
|
r.pc = op.start
|
|
continue
|
|
case TypeMacro:
|
|
var op opMacroDef
|
|
op.decode(data)
|
|
r.pc = op.endpc
|
|
continue
|
|
}
|
|
r.pc.data += n
|
|
r.pc.refs += nrefs
|
|
return EncodedOp{Key: key, Data: data, Refs: refs}, true
|
|
}
|
|
}
|
|
|
|
func (op *opMacroDef) decode(data []byte) {
|
|
if OpType(data[0]) != TypeMacro {
|
|
panic("invalid op")
|
|
}
|
|
bo := binary.LittleEndian
|
|
data = data[:TypeMacroLen]
|
|
dataIdx := int(int32(bo.Uint32(data[1:])))
|
|
refsIdx := int(int32(bo.Uint32(data[5:])))
|
|
*op = opMacroDef{
|
|
endpc: PC{
|
|
data: dataIdx,
|
|
refs: refsIdx,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (m *macroOp) decode(data []byte, refs []interface{}) {
|
|
if OpType(data[0]) != TypeCall {
|
|
panic("invalid op")
|
|
}
|
|
data = data[:TypeCallLen]
|
|
bo := binary.LittleEndian
|
|
*m = macroOp{
|
|
ops: refs[0].(*Ops),
|
|
start: PC{
|
|
data: int(int32(bo.Uint32(data[1:]))),
|
|
refs: int(int32(bo.Uint32(data[5:]))),
|
|
},
|
|
end: PC{
|
|
data: int(int32(bo.Uint32(data[9:]))),
|
|
refs: int(int32(bo.Uint32(data[13:]))),
|
|
},
|
|
}
|
|
}
|