Skip to content

Commit f086705

Browse files
dylandreimerinklmb
andauthored
asm: Add handling for atomic operations
As it stands, we only had proper handling of atomic add operations. `lock *(u32 *)(r1 + 0x1) += w2` was the only instruction that was properly handled. Atomic operations have the same opcode, but and are differentiated by the imm value. So far we have not been looking at the imm value, so all atomic operations look like atomic adds to us. This commit adds decoding for all current atomic operations. We handle them similarly to how we handle ISAv4 instructions which use the offset to further specify the instruction. In #1193 we expanded the opcode from a u8 to u16, which is bigger than the actual size. This allowed us to still represent functionally different opcodes in Go, even tough the kernel uses other bits of the instruction. So our opcode in Go is not identical to the opcode in the kernel. We translate during marshalling and unmarshaling. So far, we have only needed a few additional bits, but the atomic ops need 9 bits of imm to fully encode all possibilities. Since 9 + 8 > 16 we have to grow the opcode to 32 bits. During unmarshaling, we simply take the lower 9 bits of the imm shift it left by 16 bits and or it with the opcode. During marshaling this process is reversed. Signed-off-by: Dylan Reimerink <[email protected]> Co-authored-by: Lorenz Bauer <[email protected]>
1 parent 2a329aa commit f086705

File tree

5 files changed

+259
-25
lines changed

5 files changed

+259
-25
lines changed

asm/instruction.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder, platform str
6262

6363
ins.Offset = int16(bo.Uint16(data[2:4]))
6464

65+
// Convert to int32 before widening to int64
66+
// to ensure the signed bit is carried over.
67+
ins.Constant = int64(int32(bo.Uint32(data[4:8])))
68+
6569
if ins.IsBuiltinCall() {
6670
fn, err := BuiltinFuncForPlatform(platform, uint32(ins.Constant))
6771
if err != nil {
@@ -93,12 +97,14 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder, platform str
9397
ins.Offset = 0
9498
}
9599
}
100+
} else if ins.OpCode.Class() == StXClass &&
101+
ins.OpCode.Mode() == AtomicMode {
102+
// For atomic ops, part of the opcode is stored in the
103+
// constant field. Shift over 8 bytes so we can OR with the actual opcode and
104+
// apply `atomicMask` to avoid merging unknown bits that may be added in the future.
105+
ins.OpCode |= (OpCode((ins.Constant << 8)) & atomicMask)
96106
}
97107

98-
// Convert to int32 before widening to int64
99-
// to ensure the signed bit is carried over.
100-
ins.Constant = int64(int32(bo.Uint32(data[4:8])))
101-
102108
if !ins.OpCode.IsDWordLoad() {
103109
return nil
104110
}
@@ -171,6 +177,9 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
171177
return 0, fmt.Errorf("extended ALU opcodes should have an .Offset of 0: %s", ins)
172178
}
173179
ins.Offset = newOffset
180+
} else if atomic := ins.OpCode.AtomicOp(); atomic != InvalidAtomic {
181+
ins.OpCode = ins.OpCode &^ atomicMask
182+
ins.Constant = int64(atomic >> 8)
174183
}
175184

176185
op, err := ins.OpCode.bpfOpCode()
@@ -382,8 +391,8 @@ func (ins Instruction) Format(f fmt.State, c rune) {
382391
fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant)
383392
case MemMode, MemSXMode:
384393
fmt.Fprintf(f, "dst: %s src: %s off: %d imm: %d", ins.Dst, ins.Src, ins.Offset, ins.Constant)
385-
case XAddMode:
386-
fmt.Fprintf(f, "dst: %s src: %s", ins.Dst, ins.Src)
394+
case AtomicMode:
395+
fmt.Fprintf(f, "dst: %s src: %s off: %d", ins.Dst, ins.Src, ins.Offset)
387396
}
388397

389398
case cls.IsALU():

asm/instruction_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,82 @@ func (t testFDer) FD() int {
328328
return int(t)
329329
}
330330

331+
func TestAtomics(t *testing.T) {
332+
rawInsns := []byte{
333+
0xc3, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // lock *(u32 *)(r1 + 0x1) += w2
334+
0xc3, 0x21, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, // lock *(u32 *)(r1 + 0x1) &= w2
335+
0xc3, 0x21, 0x01, 0x00, 0xa0, 0x00, 0x00, 0x00, // lock *(u32 *)(r1 + 0x1) ^= w2
336+
0xc3, 0x21, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, // lock *(u32 *)(r1 + 0x1) |= w2
337+
338+
0xdb, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // lock *(u64 *)(r1 + 0x1) += r2
339+
0xdb, 0x21, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, // lock *(u64 *)(r1 + 0x1) &= r2
340+
0xdb, 0x21, 0x01, 0x00, 0xa0, 0x00, 0x00, 0x00, // lock *(u64 *)(r1 + 0x1) ^= r2
341+
0xdb, 0x21, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, // lock *(u64 *)(r1 + 0x1) |= r2
342+
343+
0xc3, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // w0 = atomic_fetch_add((u32 *)(r1 + 0x0), w0)
344+
0xc3, 0x01, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, // w0 = atomic_fetch_and((u32 *)(r1 + 0x0), w0)
345+
0xc3, 0x01, 0x00, 0x00, 0xa1, 0x00, 0x00, 0x00, // w0 = atomic_fetch_xor((u32 *)(r1 + 0x0), w0)
346+
0xc3, 0x01, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, // w0 = atomic_fetch_or((u32 *)(r1 + 0x0), w0)
347+
348+
0xdb, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // r0 = atomic_fetch_add((u64 *)(r1 + 0x0), r0)
349+
0xdb, 0x01, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, // r0 = atomic_fetch_and((u64 *)(r1 + 0x0), r0)
350+
0xdb, 0x01, 0x00, 0x00, 0xa1, 0x00, 0x00, 0x00, // r0 = atomic_fetch_xor((u64 *)(r1 + 0x0), r0)
351+
0xdb, 0x01, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, // r0 = atomic_fetch_or((u64 *)(r1 + 0x0), r0)
352+
353+
0xc3, 0x01, 0x00, 0x00, 0xe1, 0x00, 0x00, 0x00, // w0 = xchg32_32(r1 + 0x0, w0)
354+
0xdb, 0x01, 0x00, 0x00, 0xe1, 0x00, 0x00, 0x00, // r0 = xchg_64(r1 + 0x0, r0)
355+
356+
0xc3, 0x11, 0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, // w0 = cmpxchg32_32(r1 + 0x0, w0, w1)
357+
0xdb, 0x11, 0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, // r0 = cmpxchg_64(r1 + 0x0, r0, r1)
358+
}
359+
360+
insns, err := AppendInstructions(nil, bytes.NewReader(rawInsns), binary.LittleEndian, platform.Linux)
361+
if err != nil {
362+
t.Fatal(err)
363+
}
364+
365+
lines := []string{
366+
"StXAtomicAddW dst: r1 src: r2 off: 1",
367+
"StXAtomicAndW dst: r1 src: r2 off: 1",
368+
"StXAtomicXorW dst: r1 src: r2 off: 1",
369+
"StXAtomicOrW dst: r1 src: r2 off: 1",
370+
"StXAtomicAddDW dst: r1 src: r2 off: 1",
371+
"StXAtomicAndDW dst: r1 src: r2 off: 1",
372+
"StXAtomicXorDW dst: r1 src: r2 off: 1",
373+
"StXAtomicOrDW dst: r1 src: r2 off: 1",
374+
"StXAtomicFetchAddW dst: r1 src: r0 off: 0",
375+
"StXAtomicFetchAndW dst: r1 src: r0 off: 0",
376+
"StXAtomicFetchXorW dst: r1 src: r0 off: 0",
377+
"StXAtomicFetchOrW dst: r1 src: r0 off: 0",
378+
"StXAtomicFetchAddDW dst: r1 src: r0 off: 0",
379+
"StXAtomicFetchAndDW dst: r1 src: r0 off: 0",
380+
"StXAtomicFetchXorDW dst: r1 src: r0 off: 0",
381+
"StXAtomicFetchOrDW dst: r1 src: r0 off: 0",
382+
"StXAtomicXchgW dst: r1 src: r0 off: 0",
383+
"StXAtomicXchgDW dst: r1 src: r0 off: 0",
384+
"StXAtomicCmpXchgW dst: r1 src: r1 off: 0",
385+
"StXAtomicCmpXchgDW dst: r1 src: r1 off: 0",
386+
}
387+
388+
for i, ins := range insns {
389+
if want, got := lines[i], fmt.Sprint(ins); want != got {
390+
t.Errorf("Expected %q, got %q", want, got)
391+
}
392+
}
393+
394+
// Marshal and unmarshal again to make sure the instructions are
395+
// still valid.
396+
var buf bytes.Buffer
397+
err = insns.Marshal(&buf, binary.LittleEndian)
398+
if err != nil {
399+
t.Fatal(err)
400+
}
401+
402+
if !bytes.Equal(buf.Bytes(), rawInsns) {
403+
t.Error("Expected instructions to be equal after marshalling")
404+
}
405+
}
406+
331407
func TestISAv4(t *testing.T) {
332408
rawInsns := []byte{
333409
0xd7, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // r1 = bswap16 r1
@@ -355,6 +431,16 @@ func TestISAv4(t *testing.T) {
355431

356432
0x3c, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // w1 s/= w3
357433
0x9c, 0x42, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // w2 s%= w4
434+
435+
0xd3, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // w0 = load_acquire((u8 *)(r1 + 0x0))
436+
0xcb, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // w0 = load_acquire((u16 *)(r1 + 0x0))
437+
0xc3, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // w0 = load_acquire((u32 *)(r1 + 0x0))
438+
0xdb, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // r0 = load_acquire((u64 *)(r1 + 0x0))
439+
440+
0xd3, 0x21, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, // store_release((u8 *)(r1 + 0x0), w2)
441+
0xcb, 0x21, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, // store_release((u16 *)(r1 + 0x0), w2)
442+
0xc3, 0x21, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, // store_release((u32 *)(r1 + 0x0), w2)
443+
0xdb, 0x21, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, // store_release((u64 *)(r1 + 0x0), r2)
358444
}
359445

360446
insns, err := AppendInstructions(nil, bytes.NewReader(rawInsns), binary.LittleEndian, platform.Linux)
@@ -381,6 +467,14 @@ func TestISAv4(t *testing.T) {
381467
"SModReg dst: r2 src: r4",
382468
"SDivReg32 dst: r1 src: r3",
383469
"SModReg32 dst: r2 src: r4",
470+
"StXAtomicLdAcqB dst: r0 src: r1 off: 0",
471+
"StXAtomicLdAcqH dst: r0 src: r1 off: 0",
472+
"StXAtomicLdAcqW dst: r0 src: r1 off: 0",
473+
"StXAtomicLdAcqDW dst: r0 src: r1 off: 0",
474+
"StXAtomicStRelB dst: r1 src: r2 off: 0",
475+
"StXAtomicStRelH dst: r1 src: r2 off: 0",
476+
"StXAtomicStRelW dst: r1 src: r2 off: 0",
477+
"StXAtomicStRelDW dst: r1 src: r2 off: 0",
384478
}
385479

386480
for i, ins := range insns {

asm/load_store.go

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package asm
22

3+
import "fmt"
4+
35
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output load_store_string.go -type=Mode,Size
46

57
// Mode for load and store operations
@@ -26,10 +28,119 @@ const (
2628
MemMode Mode = 0x60
2729
// MemSXMode - load from memory, sign extension
2830
MemSXMode Mode = 0x80
29-
// XAddMode - add atomically across processors.
30-
XAddMode Mode = 0xc0
31+
// AtomicMode - add atomically across processors.
32+
AtomicMode Mode = 0xc0
3133
)
3234

35+
const atomicMask OpCode = 0x0001_ff00
36+
37+
type AtomicOp uint32
38+
39+
const (
40+
InvalidAtomic AtomicOp = 0xffff_ffff
41+
42+
// AddAtomic - add src to memory address dst atomically
43+
AddAtomic AtomicOp = AtomicOp(Add) << 8
44+
// FetchAdd - add src to memory address dst atomically, store result in src
45+
FetchAdd AtomicOp = AddAtomic | fetch
46+
// AndAtomic - bitwise AND src with memory address at dst atomically
47+
AndAtomic AtomicOp = AtomicOp(And) << 8
48+
// FetchAnd - bitwise AND src with memory address at dst atomically, store result in src
49+
FetchAnd AtomicOp = AndAtomic | fetch
50+
// OrAtomic - bitwise OR src with memory address at dst atomically
51+
OrAtomic AtomicOp = AtomicOp(Or) << 8
52+
// FetchOr - bitwise OR src with memory address at dst atomically, store result in src
53+
FetchOr AtomicOp = OrAtomic | fetch
54+
// XorAtomic - bitwise XOR src with memory address at dst atomically
55+
XorAtomic AtomicOp = AtomicOp(Xor) << 8
56+
// FetchXor - bitwise XOR src with memory address at dst atomically, store result in src
57+
FetchXor AtomicOp = XorAtomic | fetch
58+
59+
// Xchg - atomically exchange the old value with the new value
60+
//
61+
// src gets populated with the old value of *(size *)(dst + offset).
62+
Xchg AtomicOp = 0x0000_e000 | fetch
63+
// CmpXchg - atomically compare and exchange the old value with the new value
64+
//
65+
// Compares R0 and *(size *)(dst + offset), writes src to *(size *)(dst + offset) on match.
66+
// R0 gets populated with the old value of *(size *)(dst + offset), even if no exchange occurs.
67+
CmpXchg AtomicOp = 0x0000_f000 | fetch
68+
69+
// fetch modifier for copy-modify-write atomics
70+
fetch AtomicOp = 0x0000_0100
71+
// loadAcquire - atomically load with acquire semantics
72+
loadAcquire AtomicOp = 0x0001_0000
73+
// storeRelease - atomically store with release semantics
74+
storeRelease AtomicOp = 0x0001_1000
75+
)
76+
77+
func (op AtomicOp) String() string {
78+
var name string
79+
switch op {
80+
case AddAtomic, AndAtomic, OrAtomic, XorAtomic:
81+
name = ALUOp(op >> 8).String()
82+
case FetchAdd, FetchAnd, FetchOr, FetchXor:
83+
name = "Fetch" + ALUOp((op^fetch)>>8).String()
84+
case Xchg:
85+
name = "Xchg"
86+
case CmpXchg:
87+
name = "CmpXchg"
88+
case loadAcquire:
89+
name = "LdAcq"
90+
case storeRelease:
91+
name = "StRel"
92+
default:
93+
name = fmt.Sprintf("AtomicOp(%#x)", uint32(op))
94+
}
95+
96+
return name
97+
}
98+
99+
func (op AtomicOp) OpCode(size Size) OpCode {
100+
switch op {
101+
case AddAtomic, AndAtomic, OrAtomic, XorAtomic,
102+
FetchAdd, FetchAnd, FetchOr, FetchXor,
103+
Xchg, CmpXchg:
104+
switch size {
105+
case Byte, Half:
106+
// 8-bit and 16-bit atomic copy-modify-write atomics are not supported
107+
return InvalidOpCode
108+
}
109+
}
110+
111+
return OpCode(StXClass).SetMode(AtomicMode).SetSize(size).SetAtomicOp(op)
112+
}
113+
114+
// Mem emits `*(size *)(dst + offset) (op) src`.
115+
func (op AtomicOp) Mem(dst, src Register, size Size, offset int16) Instruction {
116+
return Instruction{
117+
OpCode: op.OpCode(size),
118+
Dst: dst,
119+
Src: src,
120+
Offset: offset,
121+
}
122+
}
123+
124+
// Emits `lock-acquire dst = *(size *)(src + offset)`.
125+
func LoadAcquire(dst, src Register, size Size, offset int16) Instruction {
126+
return Instruction{
127+
OpCode: loadAcquire.OpCode(size),
128+
Dst: dst,
129+
Src: src,
130+
Offset: offset,
131+
}
132+
}
133+
134+
// Emits `lock-release *(size *)(dst + offset) = src`.
135+
func StoreRelease(dst, src Register, size Size, offset int16) Instruction {
136+
return Instruction{
137+
OpCode: storeRelease.OpCode(size),
138+
Dst: dst,
139+
Src: src,
140+
Offset: offset,
141+
}
142+
}
143+
33144
// Size of load and store operations
34145
//
35146
// msb lsb
@@ -212,14 +323,10 @@ func StoreImm(dst Register, offset int16, value int64, size Size) Instruction {
212323

213324
// StoreXAddOp returns the OpCode to atomically add a register to a value in memory.
214325
func StoreXAddOp(size Size) OpCode {
215-
return OpCode(StXClass).SetMode(XAddMode).SetSize(size)
326+
return AddAtomic.OpCode(size)
216327
}
217328

218329
// StoreXAdd atomically adds src to *dst.
219330
func StoreXAdd(dst, src Register, size Size) Instruction {
220-
return Instruction{
221-
OpCode: StoreXAddOp(size),
222-
Dst: dst,
223-
Src: src,
224-
}
331+
return AddAtomic.Mem(dst, src, size, 0)
225332
}

asm/load_store_string.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)