Skip to content

Commit f654b11

Browse files
Dylan Reimerinklmb
Dylan Reimerink
authored andcommitted
asm: moved Instruction.Symbol and Instruction.Reference into metadata
This commit adds per-instruction metadata, which is sparse in nature, meaning that most instructions don't have metadata but some do. Metadata is linked to an instruction via a pointer which is nil by default, thus if an instruction has no metadata, at most one pointer worth of space is unused which is desirable since we expect the size of the metadata struct to grow in future commits. The metadata struct is copy-on-write, so multiple instructions can share the same metadata object after being copied, but as soon as a modification on one of the instruction's metadata is performed, the metadata object is copied so writes to a copied instruction doesn't effect the original instruction. This commit also deprecates `Instruction.Sym` in favor of `Instruction.WithSymbol`. This follows the convention of prefixing func names with `With...` if they have value receivers to make it clear to callers that this doesn't modify the instruction it is being called on.
1 parent b81c371 commit f654b11

16 files changed

+166
-109
lines changed

asm/dsl_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@ func TestDSL(t *testing.T) {
2525
OpCode: 0x04, Dst: R1, Constant: 22,
2626
}},
2727
{"JSGT.Imm", JSGT.Imm(R1, 4, "foo"), Instruction{
28-
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1, Reference: "foo",
29-
}},
28+
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1,
29+
}.WithReference("foo")},
3030
{"JSGT.Imm32", JSGT.Imm32(R1, -2, "foo"), Instruction{
31-
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1, Reference: "foo",
32-
}},
31+
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1,
32+
}.WithReference("foo")},
3333
{"JSLT.Reg", JSLT.Reg(R1, R2, "foo"), Instruction{
34-
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1, Reference: "foo",
35-
}},
34+
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1,
35+
}.WithReference("foo")},
3636
{"JSLT.Reg32", JSLT.Reg32(R1, R3, "foo"), Instruction{
37-
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1, Reference: "foo",
38-
}},
37+
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1,
38+
}.WithReference("foo")},
3939
}
4040

4141
for _, tc := range testcases {
42-
if tc.have != tc.want {
42+
if !tc.have.equal(tc.want) {
4343
t.Errorf("%s: have %v, want %v", tc.name, tc.have, tc.want)
4444
}
4545
}

asm/instruction.go

+98-29
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,39 @@ type Instruction struct {
3535
Offset int16
3636
Constant int64
3737

38-
// Reference denotes a reference (e.g. a jump) to another symbol.
39-
Reference string
38+
// Metadata contains optional metadata about this instruction
39+
metadata *metadata
40+
}
41+
42+
// WithSymbol marks the Instruction as a Symbol, which other Instructions
43+
// can point to using corresponding calls to WithReference.
44+
func (ins Instruction) WithSymbol(name string) Instruction {
45+
if (ins.metadata != nil && ins.metadata.symbol == name) ||
46+
(ins.metadata == nil && name == "") {
47+
return ins
48+
}
4049

41-
// Symbol denotes an instruction at the start of a function body.
42-
Symbol string
50+
ins.metadata = ins.metadata.copy()
51+
ins.metadata.symbol = name
52+
return ins
4353
}
4454

4555
// Sym creates a symbol.
56+
//
57+
// Deprecated: use WithSymbol instead.
4658
func (ins Instruction) Sym(name string) Instruction {
47-
ins.Symbol = name
48-
return ins
59+
return ins.WithSymbol(name)
60+
}
61+
62+
// Symbol returns the value ins has been marked with using WithSymbol,
63+
// otherwise returns an empty string. A symbol is often an Instruction
64+
// at the start of a function body.
65+
func (ins Instruction) Symbol() string {
66+
if ins.metadata == nil {
67+
return ""
68+
}
69+
70+
return ins.metadata.symbol
4971
}
5072

5173
// Unmarshal decodes a BPF instruction.
@@ -299,16 +321,63 @@ func (ins Instruction) Format(f fmt.State, c rune) {
299321
}
300322

301323
ref:
302-
if ins.Reference != "" {
303-
fmt.Fprintf(f, " <%s>", ins.Reference)
324+
if ins.Reference() != "" {
325+
fmt.Fprintf(f, " <%s>", ins.Reference())
304326
}
305327
}
306328

329+
func (ins Instruction) equal(other Instruction) bool {
330+
return ins.OpCode == other.OpCode &&
331+
ins.Dst == other.Dst &&
332+
ins.Src == other.Src &&
333+
ins.Offset == other.Offset &&
334+
ins.Constant == other.Constant
335+
}
336+
307337
// Size returns the amount of bytes ins would occupy in binary form.
308338
func (ins Instruction) Size() uint64 {
309339
return uint64(InstructionSize * ins.OpCode.rawInstructions())
310340
}
311341

342+
// WithReference makes ins reference another Symbol or map by name.
343+
func (ins Instruction) WithReference(ref string) Instruction {
344+
if (ins.metadata != nil && ins.metadata.reference == ref) ||
345+
(ins.metadata == nil && ref == "") {
346+
return ins
347+
}
348+
349+
ins.metadata = ins.metadata.copy()
350+
ins.metadata.reference = ref
351+
return ins
352+
}
353+
354+
// Reference returns the Symbol or map name referenced by ins, if any.
355+
func (ins Instruction) Reference() string {
356+
if ins.metadata == nil {
357+
return ""
358+
}
359+
360+
return ins.metadata.reference
361+
}
362+
363+
// metadata holds metadata about an Instruction.
364+
type metadata struct {
365+
// reference denotes a reference (e.g. a jump) to another symbol.
366+
reference string
367+
// symbol denotes an instruction at the start of a function body.
368+
symbol string
369+
}
370+
371+
// copy returns a copy of metadata.
372+
// Always returns a valid pointer, even when called on a nil metadata.
373+
func (m *metadata) copy() *metadata {
374+
var copy metadata
375+
if m != nil {
376+
copy = *m
377+
}
378+
return &copy
379+
}
380+
312381
// Instructions is an eBPF program.
313382
type Instructions []Instruction
314383

@@ -342,7 +411,7 @@ func (insns Instructions) Name() string {
342411
if len(insns) == 0 {
343412
return ""
344413
}
345-
return insns[0].Symbol
414+
return insns[0].Symbol()
346415
}
347416

348417
func (insns Instructions) String() string {
@@ -369,7 +438,7 @@ func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
369438
found := false
370439
for i := range insns {
371440
ins := &insns[i]
372-
if ins.Reference != symbol {
441+
if ins.Reference() != symbol {
373442
continue
374443
}
375444

@@ -393,15 +462,15 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
393462
offsets := make(map[string]int)
394463

395464
for i, ins := range insns {
396-
if ins.Symbol == "" {
465+
if ins.Symbol() == "" {
397466
continue
398467
}
399468

400-
if _, ok := offsets[ins.Symbol]; ok {
401-
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
469+
if _, ok := offsets[ins.Symbol()]; ok {
470+
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol())
402471
}
403472

404-
offsets[ins.Symbol] = i
473+
offsets[ins.Symbol()] = i
405474
}
406475

407476
return offsets, nil
@@ -418,15 +487,15 @@ func (insns Instructions) FunctionReferences() map[string]bool {
418487
continue
419488
}
420489

421-
if ins.Reference == "" {
490+
if ins.Reference() == "" {
422491
continue
423492
}
424493

425494
if !ins.IsFunctionReference() {
426495
continue
427496
}
428497

429-
calls[ins.Reference] = true
498+
calls[ins.Reference()] = true
430499
}
431500

432501
return calls
@@ -438,11 +507,11 @@ func (insns Instructions) ReferenceOffsets() map[string][]int {
438507
offsets := make(map[string][]int)
439508

440509
for i, ins := range insns {
441-
if ins.Reference == "" {
510+
if ins.Reference() == "" {
442511
continue
443512
}
444513

445-
offsets[ins.Reference] = append(offsets[ins.Reference], i)
514+
offsets[ins.Reference()] = append(offsets[ins.Reference()], i)
446515
}
447516

448517
return offsets
@@ -493,8 +562,8 @@ func (insns Instructions) Format(f fmt.State, c rune) {
493562

494563
iter := insns.Iterate()
495564
for iter.Next() {
496-
if iter.Ins.Symbol != "" {
497-
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol)
565+
if iter.Ins.Symbol() != "" {
566+
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol())
498567
}
499568
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
500569
}
@@ -552,15 +621,15 @@ func (insns Instructions) resolveFunctionReferences() error {
552621
for iter.Next() {
553622
ins := iter.Ins
554623

555-
if ins.Symbol == "" {
624+
if ins.Symbol() == "" {
556625
continue
557626
}
558627

559-
if _, ok := symbolOffsets[ins.Symbol]; ok {
560-
return fmt.Errorf("duplicate symbol %s", ins.Symbol)
628+
if _, ok := symbolOffsets[ins.Symbol()]; ok {
629+
return fmt.Errorf("duplicate symbol %s", ins.Symbol())
561630
}
562631

563-
symbolOffsets[ins.Symbol] = iter.Offset
632+
symbolOffsets[ins.Symbol()] = iter.Offset
564633
}
565634

566635
// Find all instructions tagged as references to other symbols.
@@ -572,23 +641,23 @@ func (insns Instructions) resolveFunctionReferences() error {
572641
offset := iter.Offset
573642
ins := iter.Ins
574643

575-
if ins.Reference == "" {
644+
if ins.Reference() == "" {
576645
continue
577646
}
578647

579648
switch {
580649
case ins.IsFunctionReference() && ins.Constant == -1:
581-
symOffset, ok := symbolOffsets[ins.Reference]
650+
symOffset, ok := symbolOffsets[ins.Reference()]
582651
if !ok {
583-
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference, ErrUnsatisfiedProgramReference)
652+
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
584653
}
585654

586655
ins.Constant = int64(symOffset - offset - 1)
587656

588657
case ins.OpCode.Class().IsJump() && ins.Offset == -1:
589-
symOffset, ok := symbolOffsets[ins.Reference]
658+
symOffset, ok := symbolOffsets[ins.Reference()]
590659
if !ok {
591-
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference, ErrUnsatisfiedProgramReference)
660+
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
592661
}
593662

594663
ins.Offset = int16(symOffset - offset - 1)

asm/instruction_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,9 @@ func TestInstructionLoadMapValue(t *testing.T) {
151151

152152
func TestInstructionsRewriteMapPtr(t *testing.T) {
153153
insns := Instructions{
154-
LoadMapPtr(R1, 0),
154+
LoadMapPtr(R1, 0).WithReference("good"),
155155
Return(),
156156
}
157-
insns[0].Reference = "good"
158157

159158
if err := insns.RewriteMapPtr("good", 1); err != nil {
160159
t.Fatal(err)
@@ -181,7 +180,7 @@ func TestInstructionsRewriteMapPtr(t *testing.T) {
181180
// program is stringified.
182181
func ExampleInstructions_Format() {
183182
insns := Instructions{
184-
FnMapLookupElem.Call().Sym("my_func"),
183+
FnMapLookupElem.Call().WithSymbol("my_func"),
185184
LoadImm(R0, 42, DWord),
186185
Return(),
187186
}

asm/jump.go

+27-33
Original file line numberDiff line numberDiff line change
@@ -63,47 +63,43 @@ func (op JumpOp) Op(source Source) OpCode {
6363
// Imm compares 64 bit dst to 64 bit value (sign extended), and adjusts PC by offset if the condition is fulfilled.
6464
func (op JumpOp) Imm(dst Register, value int32, label string) Instruction {
6565
return Instruction{
66-
OpCode: op.opCode(JumpClass, ImmSource),
67-
Dst: dst,
68-
Offset: -1,
69-
Constant: int64(value),
70-
Reference: label,
71-
}
66+
OpCode: op.opCode(JumpClass, ImmSource),
67+
Dst: dst,
68+
Offset: -1,
69+
Constant: int64(value),
70+
}.WithReference(label)
7271
}
7372

7473
// Imm32 compares 32 bit dst to 32 bit value, and adjusts PC by offset if the condition is fulfilled.
7574
// Requires kernel 5.1.
7675
func (op JumpOp) Imm32(dst Register, value int32, label string) Instruction {
7776
return Instruction{
78-
OpCode: op.opCode(Jump32Class, ImmSource),
79-
Dst: dst,
80-
Offset: -1,
81-
Constant: int64(value),
82-
Reference: label,
83-
}
77+
OpCode: op.opCode(Jump32Class, ImmSource),
78+
Dst: dst,
79+
Offset: -1,
80+
Constant: int64(value),
81+
}.WithReference(label)
8482
}
8583

8684
// Reg compares 64 bit dst to 64 bit src, and adjusts PC by offset if the condition is fulfilled.
8785
func (op JumpOp) Reg(dst, src Register, label string) Instruction {
8886
return Instruction{
89-
OpCode: op.opCode(JumpClass, RegSource),
90-
Dst: dst,
91-
Src: src,
92-
Offset: -1,
93-
Reference: label,
94-
}
87+
OpCode: op.opCode(JumpClass, RegSource),
88+
Dst: dst,
89+
Src: src,
90+
Offset: -1,
91+
}.WithReference(label)
9592
}
9693

9794
// Reg32 compares 32 bit dst to 32 bit src, and adjusts PC by offset if the condition is fulfilled.
9895
// Requires kernel 5.1.
9996
func (op JumpOp) Reg32(dst, src Register, label string) Instruction {
10097
return Instruction{
101-
OpCode: op.opCode(Jump32Class, RegSource),
102-
Dst: dst,
103-
Src: src,
104-
Offset: -1,
105-
Reference: label,
106-
}
98+
OpCode: op.opCode(Jump32Class, RegSource),
99+
Dst: dst,
100+
Src: src,
101+
Offset: -1,
102+
}.WithReference(label)
107103
}
108104

109105
func (op JumpOp) opCode(class Class, source Source) OpCode {
@@ -118,16 +114,14 @@ func (op JumpOp) opCode(class Class, source Source) OpCode {
118114
func (op JumpOp) Label(label string) Instruction {
119115
if op == Call {
120116
return Instruction{
121-
OpCode: OpCode(JumpClass).SetJumpOp(Call),
122-
Src: PseudoCall,
123-
Constant: -1,
124-
Reference: label,
125-
}
117+
OpCode: OpCode(JumpClass).SetJumpOp(Call),
118+
Src: PseudoCall,
119+
Constant: -1,
120+
}.WithReference(label)
126121
}
127122

128123
return Instruction{
129-
OpCode: OpCode(JumpClass).SetJumpOp(op),
130-
Offset: -1,
131-
Reference: label,
132-
}
124+
OpCode: OpCode(JumpClass).SetJumpOp(op),
125+
Offset: -1,
126+
}.WithReference(label)
133127
}

0 commit comments

Comments
 (0)