Skip to content

Commit 8669dd4

Browse files
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 <dylan.reimerink@isovalent.com>
1 parent e3234a1 commit 8669dd4

5 files changed

Lines changed: 287 additions & 25 deletions

File tree

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: 143 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,147 @@ 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
33+
)
34+
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+
// AndAtomic - bitwise AND src with memory address at dst atomically
45+
AndAtomic AtomicOp = AtomicOp(And) << 8
46+
// OrAtomic - bitwise OR src with memory address at dst atomically
47+
OrAtomic AtomicOp = AtomicOp(Or) << 8
48+
// XorAtomic - bitwise XOR src with memory address at dst atomically
49+
XorAtomic AtomicOp = AtomicOp(Xor) << 8
50+
51+
// xchgAtomic - atomically exchange the old value with the new value
52+
xchgAtomic AtomicOp = 0x0000_e000
53+
// cmpXchgAtomic - atomically compare and exchange the old value with the new value
54+
cmpXchgAtomic AtomicOp = 0x0000_f000
55+
56+
// fetch modifier for copy-modify-write atomics
57+
fetch AtomicOp = 0x0000_0100
58+
// loadAcquireAtomic - atomically load with acquire semantics
59+
loadAcquireAtomic AtomicOp = 0x0001_0000
60+
// storeReleaseAtomic - atomically store with release semantics
61+
storeReleaseAtomic AtomicOp = 0x0001_1000
3162
)
3263

64+
func (op AtomicOp) String() string {
65+
var name string
66+
switch op {
67+
case AddAtomic, AndAtomic, OrAtomic, XorAtomic:
68+
name = ALUOp(op >> 8).String()
69+
case AddAtomic | fetch, AndAtomic | fetch, OrAtomic | fetch, XorAtomic | fetch:
70+
name = "Fetch" + ALUOp((op^fetch)>>8).String()
71+
case xchgAtomic | fetch:
72+
name = "Xchg"
73+
case cmpXchgAtomic | fetch:
74+
name = "CmpXchg"
75+
case loadAcquireAtomic:
76+
name = "LdAcq"
77+
case storeReleaseAtomic:
78+
name = "StRel"
79+
default:
80+
name = fmt.Sprintf("AtomicOp(%#x)", uint32(op))
81+
}
82+
83+
return name
84+
}
85+
86+
func (op AtomicOp) OpCode(size Size) OpCode {
87+
switch op {
88+
case AddAtomic, AndAtomic, OrAtomic, XorAtomic,
89+
AddAtomic | fetch, AndAtomic | fetch, OrAtomic | fetch, XorAtomic | fetch,
90+
xchgAtomic | fetch, cmpXchgAtomic | fetch:
91+
switch size {
92+
case Byte, Half:
93+
// 8-bit and 16-bit atomic copy-modify-write atomics are not supported
94+
return InvalidOpCode
95+
}
96+
}
97+
98+
return OpCode(StXClass).SetMode(AtomicMode).SetSize(size).SetAtomicOp(op)
99+
}
100+
101+
// Mem emits `*(size *)(dst + offset) (op) src`.
102+
func (op AtomicOp) Mem(dst, src Register, size Size, offset int16) Instruction {
103+
switch op {
104+
case xchgAtomic, cmpXchgAtomic:
105+
// XchgAtomic and CmpXchgAtomic always have fetch set, FetchMem must be used
106+
return Instruction{
107+
OpCode: InvalidOpCode,
108+
Dst: dst,
109+
Src: src,
110+
Offset: offset,
111+
}
112+
}
113+
114+
return Instruction{
115+
OpCode: op.OpCode(size),
116+
Dst: dst,
117+
Src: src,
118+
Offset: offset,
119+
}
120+
}
121+
122+
// FetchMem is like Mem but also stores the result in src.
123+
func (op AtomicOp) FetchMem(dst, src Register, size Size, offset int16) Instruction {
124+
fetchOp := op | fetch
125+
ins := fetchOp.Mem(src, dst, size, offset)
126+
return ins
127+
}
128+
129+
// Emits `lock-acquire dst = *(size *)(src + offset)`.
130+
func LoadAcquire(dst, src Register, size Size, offset int16) Instruction {
131+
return Instruction{
132+
OpCode: loadAcquireAtomic.OpCode(size),
133+
Dst: dst,
134+
Src: src,
135+
Offset: offset,
136+
}
137+
}
138+
139+
// Emits `lock-release *(size *)(dst + offset) = src`.
140+
func StoreRelease(dst, src Register, size Size, offset int16) Instruction {
141+
return Instruction{
142+
OpCode: storeReleaseAtomic.OpCode(size),
143+
Dst: dst,
144+
Src: src,
145+
Offset: offset,
146+
}
147+
}
148+
149+
// Emits `src = xchg(*(size *)(dst + offset), src)`.
150+
// src gets populated with the old value of *(size *)(dst + offset).
151+
func AtomicXchg(dst, src Register, size Size, offset int16, fetch bool) Instruction {
152+
return Instruction{
153+
OpCode: xchgAtomic.OpCode(size),
154+
Dst: dst,
155+
Src: src,
156+
Offset: offset,
157+
}
158+
}
159+
160+
// Emits `r0 = cmpxchg(*(size *)(dst + offset), r0, src)`.
161+
// Compares R0 and *(size *)(dst + offset), writes src to *(size *)(dst + offset) on match.
162+
// R0 gets populated with the old value of *(size *)(dst + offset), even if no exchange occurs.
163+
func AtomicCmpXchg(dst, src Register, size Size, offset int16, fetch bool) Instruction {
164+
return Instruction{
165+
OpCode: cmpXchgAtomic.OpCode(size),
166+
Dst: dst,
167+
Src: src,
168+
Offset: offset,
169+
}
170+
}
171+
33172
// Size of load and store operations
34173
//
35174
// msb lsb
@@ -212,14 +351,10 @@ func StoreImm(dst Register, offset int16, value int64, size Size) Instruction {
212351

213352
// StoreXAddOp returns the OpCode to atomically add a register to a value in memory.
214353
func StoreXAddOp(size Size) OpCode {
215-
return OpCode(StXClass).SetMode(XAddMode).SetSize(size)
354+
return AddAtomic.OpCode(size)
216355
}
217356

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

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)