Skip to content

Commit

Permalink
WIP asm: introduce metadata
Browse files Browse the repository at this point in the history
Storing Reference and Symbol in every instruction increases the
size of the type by 32 bytes. This is wasteful considering that most instructions
carry neither. Introduce a metadata type that add only a single machine
word to each instruction, and which can store arbitrary metadata.
  • Loading branch information
lmb committed Feb 10, 2022
1 parent 16db4a4 commit 6af8ec0
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 88 deletions.
15 changes: 8 additions & 7 deletions asm/dsl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package asm

import (
"testing"

qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp/cmpopts"
)

func TestDSL(t *testing.T) {
Expand All @@ -25,22 +28,20 @@ func TestDSL(t *testing.T) {
OpCode: 0x04, Dst: R1, Constant: 22,
}},
{"JSGT.Imm", JSGT.Imm(R1, 4, "foo"), Instruction{
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1, Reference: "foo",
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1,
}},
{"JSGT.Imm32", JSGT.Imm32(R1, -2, "foo"), Instruction{
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1, Reference: "foo",
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1,
}},
{"JSLT.Reg", JSLT.Reg(R1, R2, "foo"), Instruction{
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1, Reference: "foo",
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1,
}},
{"JSLT.Reg32", JSLT.Reg32(R1, R3, "foo"), Instruction{
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1, Reference: "foo",
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1,
}},
}

for _, tc := range testcases {
if tc.have != tc.want {
t.Errorf("%s: have %v, want %v", tc.name, tc.have, tc.want)
}
qt.Assert(t, tc.have, qt.CmpEquals(cmpopts.IgnoreUnexported(Instruction{})), tc.want)
}
}
68 changes: 44 additions & 24 deletions asm/instruction.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,38 @@ type Instruction struct {
Src Register
Offset int16
Constant int64
meta meta
}

type reference struct{}

// Reference denotes a reference (e.g. a jump) to another symbol.
Reference string
// Reference denotes a reference (e.g. a jump) to another symbol.
func (ins *Instruction) Reference() string {
ref, _ := ins.meta[reference{}].(string)
return ref
}

// Symbol denotes an instruction at the start of a function body.
Symbol string
func (ins *Instruction) SetReference(ref string) {
ins.meta.add(reference{}, ref)
}

type symbol struct{}

// Symbol denotes an instruction at the start of a function body.
func (ins *Instruction) Symbol() string {
sym, _ := ins.meta[symbol{}].(string)
return sym
}

func (ins *Instruction) SetSymbol(sym string) {
ins.meta.add(symbol{}, sym)
}

// Sym creates a symbol.
//
// The original instruction is left unmodified.
func (ins Instruction) Sym(name string) Instruction {
ins.Symbol = name
ins.SetSymbol(name)
return ins
}

Expand Down Expand Up @@ -137,11 +158,7 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
//
// Returns an error if the instruction doesn't load a map.
func (ins *Instruction) RewriteMapPtr(fd int) error {
if !ins.OpCode.IsDWordLoad() {
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
}

if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue {
if !ins.IsLoadFromMap() {
return errors.New("not a load from a map")
}

Expand Down Expand Up @@ -296,8 +313,8 @@ func (ins Instruction) Format(f fmt.State, c rune) {
}

ref:
if ins.Reference != "" {
fmt.Fprintf(f, " <%s>", ins.Reference)
if ref := ins.Reference(); ref != "" {
fmt.Fprintf(f, " <%s>", ref)
}
}

Expand Down Expand Up @@ -339,7 +356,7 @@ func (insns Instructions) Name() string {
if len(insns) == 0 {
return ""
}
return insns[0].Symbol
return insns[0].Symbol()
}

func (insns Instructions) String() string {
Expand All @@ -366,7 +383,7 @@ func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
found := false
for i := range insns {
ins := &insns[i]
if ins.Reference != symbol {
if ins.Reference() != symbol {
continue
}

Expand All @@ -390,15 +407,16 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
offsets := make(map[string]int)

for i, ins := range insns {
if ins.Symbol == "" {
sym := ins.Symbol()
if sym == "" {
continue
}

if _, ok := offsets[ins.Symbol]; ok {
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
if _, ok := offsets[sym]; ok {
return nil, fmt.Errorf("duplicate symbol %s", sym)
}

offsets[ins.Symbol] = i
offsets[sym] = i
}

return offsets, nil
Expand All @@ -415,15 +433,16 @@ func (insns Instructions) FunctionReferences() map[string]bool {
continue
}

if ins.Reference == "" {
ref := ins.Reference()
if ref == "" {
continue
}

if !ins.IsFunctionReference() {
continue
}

calls[ins.Reference] = true
calls[ref] = true
}

return calls
Expand All @@ -435,11 +454,12 @@ func (insns Instructions) ReferenceOffsets() map[string][]int {
offsets := make(map[string][]int)

for i, ins := range insns {
if ins.Reference == "" {
ref := ins.Reference()
if ref == "" {
continue
}

offsets[ins.Reference] = append(offsets[ins.Reference], i)
offsets[ref] = append(offsets[ref], i)
}

return offsets
Expand Down Expand Up @@ -490,8 +510,8 @@ func (insns Instructions) Format(f fmt.State, c rune) {

iter := insns.Iterate()
for iter.Next() {
if iter.Ins.Symbol != "" {
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol)
if sym := iter.Ins.Symbol(); sym != "" {
fmt.Fprintf(f, "%s%s:\n", symIndent, sym)
}
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
}
Expand Down
2 changes: 1 addition & 1 deletion asm/instruction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func TestInstructionsRewriteMapPtr(t *testing.T) {
LoadMapPtr(R1, 0),
Return(),
}
insns[0].Reference = "good"
insns[0].SetReference("good")

if err := insns.RewriteMapPtr("good", 1); err != nil {
t.Fatal(err)
Expand Down
54 changes: 27 additions & 27 deletions asm/jump.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,46 +63,46 @@ func (op JumpOp) Op(source Source) OpCode {
// Imm compares 64 bit dst to 64 bit value (sign extended), and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Imm(dst Register, value int32, label string) Instruction {
return Instruction{
OpCode: op.opCode(JumpClass, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
Reference: label,
OpCode: op.opCode(JumpClass, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
meta: meta{reference{}: label},
}
}

// Imm32 compares 32 bit dst to 32 bit value, and adjusts PC by offset if the condition is fulfilled.
// Requires kernel 5.1.
func (op JumpOp) Imm32(dst Register, value int32, label string) Instruction {
return Instruction{
OpCode: op.opCode(Jump32Class, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
Reference: label,
OpCode: op.opCode(Jump32Class, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
meta: meta{reference{}: label},
}
}

// Reg compares 64 bit dst to 64 bit src, and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Reg(dst, src Register, label string) Instruction {
return Instruction{
OpCode: op.opCode(JumpClass, RegSource),
Dst: dst,
Src: src,
Offset: -1,
Reference: label,
OpCode: op.opCode(JumpClass, RegSource),
Dst: dst,
Src: src,
Offset: -1,
meta: meta{reference{}: label},
}
}

// Reg32 compares 32 bit dst to 32 bit src, and adjusts PC by offset if the condition is fulfilled.
// Requires kernel 5.1.
func (op JumpOp) Reg32(dst, src Register, label string) Instruction {
return Instruction{
OpCode: op.opCode(Jump32Class, RegSource),
Dst: dst,
Src: src,
Offset: -1,
Reference: label,
OpCode: op.opCode(Jump32Class, RegSource),
Dst: dst,
Src: src,
Offset: -1,
meta: meta{reference{}: label},
}
}

Expand All @@ -118,16 +118,16 @@ func (op JumpOp) opCode(class Class, source Source) OpCode {
func (op JumpOp) Label(label string) Instruction {
if op == Call {
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(Call),
Src: PseudoCall,
Constant: -1,
Reference: label,
OpCode: OpCode(JumpClass).SetJumpOp(Call),
Src: PseudoCall,
Constant: -1,
meta: meta{reference{}: label},
}
}

return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(op),
Offset: -1,
Reference: label,
OpCode: OpCode(JumpClass).SetJumpOp(op),
Offset: -1,
meta: meta{reference{}: label},
}
}
33 changes: 33 additions & 0 deletions asm/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package asm

// meta is a way to attach metadata to an instruction.
//
// An empty metadata only takes up a single machine word, so this is
// useful for cases where metadata is mostly empty.
type meta map[interface{}]interface{}

// add a value to the metadata set.
//
// Avoids modifying old metadata by copying if necessary.
func (m *meta) add(key, value interface{}) {
have, ok := (*m)[key]
n := len(*m) + 1
if ok {
if have == value {
// We already have this key with the same value, so we don't
// need to do anything.
return
}

n--
}

// There is no such key, or the value is different.
// Create a copy and overwrite the entry for key.
cpy := make(meta, n)
for t, v := range *m {
cpy[t] = v
}
cpy[key] = value
*m = cpy
}
60 changes: 60 additions & 0 deletions asm/metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package asm

import (
"testing"
"unsafe"

qt "github.com/frankban/quicktest"
)

func TestMetadata(t *testing.T) {
var m meta

t.Log("size:", unsafe.Sizeof(m))

// A lookup in a nil meta should return nil.
qt.Assert(t, m[bool(false)], qt.IsNil)

// We can look up anything we inserted.
m.add(bool(false), int(0))
qt.Assert(t, m, qt.HasLen, 1)
qt.Assert(t, m[bool(false)], qt.Equals, int(0))

// We have copy on write semantics
old := m
m.add(bool(false), int(1))
qt.Assert(t, m, qt.HasLen, 1)
qt.Assert(t, m[bool(false)], qt.Equals, int(1))
qt.Assert(t, old[bool(false)], qt.Equals, int(0))

// Newtypes are handled distinctly.
type b bool
m.add(b(false), int(42))
qt.Assert(t, m, qt.HasLen, 2)
qt.Assert(t, m[bool(false)], qt.Equals, int(1))
qt.Assert(t, m[b(false)], qt.Equals, int(42))
}

func BenchmarkMetadata(b *testing.B) {
type k struct{}

b.Run("add", func(b *testing.B) {
m := meta{0: 0, 1: 1, 2: 2, 3: 3}
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
m.add(k{}, b.N)
}
})

b.Run("add existing", func(b *testing.B) {
m := meta{0: 0, 1: 1, 2: 2, k{}: 0}
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
m.add(k{}, 0)
}
})
}
Loading

0 comments on commit 6af8ec0

Please sign in to comment.