Skip to content

Commit 6cb882d

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 6cb882d

5 files changed

Lines changed: 322 additions & 25 deletions

File tree

asm/instruction.go

Lines changed: 14 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,13 @@ 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, consider the lower 16 bits of the constant as part of the opcode
103+
// it tells us which sort of atomic operation we are doing.
104+
ins.OpCode |= (OpCode((ins.Constant << 16)) & atomicMask)
96105
}
97106

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-
102107
if !ins.OpCode.IsDWordLoad() {
103108
return nil
104109
}
@@ -171,6 +176,9 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
171176
return 0, fmt.Errorf("extended ALU opcodes should have an .Offset of 0: %s", ins)
172177
}
173178
ins.Offset = newOffset
179+
} else if atomic := ins.OpCode.Atomic(); atomic != InvalidAtomic {
180+
ins.OpCode = ins.OpCode &^ atomicMask
181+
ins.Constant = int64(atomic >> 16)
174182
}
175183

176184
op, err := ins.OpCode.bpfOpCode()
@@ -382,8 +390,8 @@ func (ins Instruction) Format(f fmt.State, c rune) {
382390
fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant)
383391
case MemMode, MemSXMode:
384392
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)
393+
case AtomicMode:
394+
fmt.Fprintf(f, "dst: %s src: %s off: %d", ins.Dst, ins.Src, ins.Offset)
387395
}
388396

389397
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+
0xc3, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // lock *(u64 *)(r1 + 0x1) += r2
339+
0xc3, 0x21, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, // lock *(u64 *)(r1 + 0x1) &= r2
340+
0xc3, 0x21, 0x01, 0x00, 0xa0, 0x00, 0x00, 0x00, // lock *(u64 *)(r1 + 0x1) ^= r2
341+
0xc3, 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: 133 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package asm
22

3-
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output load_store_string.go -type=Mode,Size
3+
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output load_store_string.go -type=Mode,Size,Atomic
44

55
// Mode for load and store operations
66
//
@@ -26,8 +26,42 @@ const (
2626
MemMode Mode = 0x60
2727
// MemSXMode - load from memory, sign extension
2828
MemSXMode Mode = 0x80
29-
// XAddMode - add atomically across processors.
30-
XAddMode Mode = 0xc0
29+
// AtomicMode - add atomically across processors.
30+
AtomicMode Mode = 0xc0
31+
)
32+
33+
const atomicMask OpCode = 0x01ff_0000
34+
35+
type Atomic uint32
36+
37+
const (
38+
InvalidAtomic Atomic = 0xffff_ffff
39+
// AddAtomic - add src to memory address dst atomically
40+
AddAtomic Atomic = Atomic(Add) << 16
41+
// FetchAddAtomic - add src to memory address at dst atomically, store old value in src
42+
FetchAddAtomic Atomic = AddAtomic | Fetch
43+
// AndAtomic - bitwise AND src with memory address at dst atomically
44+
AndAtomic Atomic = Atomic(And) << 16
45+
// FetchAndAtomic - bitwise AND src with memory address at dst atomically, store old value in src
46+
FetchAndAtomic Atomic = AndAtomic | Fetch
47+
// OrAtomic - bitwise OR src with memory address at dst atomically
48+
OrAtomic Atomic = Atomic(Or) << 16
49+
// FetchOrAtomic - bitwise OR src with memory address at dst atomically, store old value in src
50+
FetchOrAtomic Atomic = OrAtomic | Fetch
51+
// XorAtomic - bitwise XOR src with memory address at dst atomically
52+
XorAtomic Atomic = Atomic(Xor) << 16
53+
// FetchXorAtomic - bitwise XOR src with memory address at dst atomically, store old value in src
54+
FetchXorAtomic Atomic = XorAtomic | Fetch
55+
// FetchAtomicOp - load the old value into the src register
56+
Fetch Atomic = 0x0001_0000
57+
// XchgAtomic - atomically exchange the old value with the new value
58+
XchgAtomic Atomic = 0x00e0_0000 | Fetch
59+
// CmpXchgAtomic - atomically compare and exchange the old value with the new value
60+
CmpXchgAtomic Atomic = 0x00f0_0000 | Fetch
61+
// LdAcqAtomic - atomically load with acquire semantics
62+
LdAcqAtomic Atomic = 0x0100_0000
63+
// StRelAtomic - atomically store with release semantics
64+
StRelAtomic Atomic = 0x0110_0000
3165
)
3266

3367
// Size of load and store operations
@@ -210,16 +244,109 @@ func StoreImm(dst Register, offset int16, value int64, size Size) Instruction {
210244
}
211245
}
212246

247+
func AtomicOp(size Size, atomic Atomic, fetch bool) OpCode {
248+
switch atomic {
249+
case AddAtomic, AndAtomic, OrAtomic, XorAtomic:
250+
if fetch {
251+
atomic |= Fetch
252+
}
253+
254+
switch size {
255+
case Byte, Half:
256+
// 8-bit and 16-bit atomic copy-modify-write atomics are not supported
257+
return InvalidOpCode
258+
}
259+
}
260+
261+
return OpCode(StXClass).SetMode(AtomicMode).SetSize(size).SetAtomic(atomic)
262+
}
263+
213264
// StoreXAddOp returns the OpCode to atomically add a register to a value in memory.
214265
func StoreXAddOp(size Size) OpCode {
215-
return OpCode(StXClass).SetMode(XAddMode).SetSize(size)
266+
return AtomicOp(size, AddAtomic, false)
216267
}
217268

218-
// StoreXAdd atomically adds src to *dst.
269+
// AtomicAnd atomically adds src to *(dst + offset), storing the old value in src if fetch is true.
270+
func AtomicAdd(dst, src Register, size Size, offset int16, fetch bool) Instruction {
271+
return Instruction{
272+
OpCode: AtomicOp(size, AddAtomic, fetch),
273+
Dst: dst,
274+
Src: src,
275+
Offset: offset,
276+
}
277+
}
278+
279+
// Alias for AtomicAdd
219280
func StoreXAdd(dst, src Register, size Size) Instruction {
281+
return AtomicAdd(dst, src, size, 0, false)
282+
}
283+
284+
// AtomicAnd atomically bitwise ANDs src with *(dst + offset), storing the old value in src if fetch is true.
285+
func AtomicAnd(dst, src Register, size Size, offset int16, fetch bool) Instruction {
286+
return Instruction{
287+
OpCode: AtomicOp(size, AndAtomic, fetch),
288+
Dst: dst,
289+
Src: src,
290+
Offset: offset,
291+
}
292+
}
293+
294+
// AtomicOr atomically bitwise ORs src with *(dst + offset), storing the old value in src if fetch is true.
295+
func AtomicOr(dst, src Register, size Size, offset int16, fetch bool) Instruction {
296+
return Instruction{
297+
OpCode: AtomicOp(size, OrAtomic, fetch),
298+
Dst: dst,
299+
Src: src,
300+
Offset: offset,
301+
}
302+
}
303+
304+
// AtomicXor atomically bitwise XORs src with *(dst + offset), storing the old value in src if fetch is true.
305+
func AtomicXor(dst, src Register, size Size, offset int16, fetch bool) Instruction {
306+
return Instruction{
307+
OpCode: AtomicOp(size, XorAtomic, fetch),
308+
Dst: dst,
309+
Src: src,
310+
Offset: offset,
311+
}
312+
}
313+
314+
// AtomicXchg atomically exchanges src with *(dst + offset), storing the old value in src if fetch is true.
315+
func AtomicXchg(dst, src Register, size Size, offset int16, fetch bool) Instruction {
316+
return Instruction{
317+
OpCode: AtomicOp(size, XchgAtomic, fetch),
318+
Dst: dst,
319+
Src: src,
320+
Offset: offset,
321+
}
322+
}
323+
324+
// AtomicCmpXchg atomically compares src with *(dst + offset), storing the old value in src if fetch is true.
325+
func AtomicCmpXchg(dst, src Register, size Size, offset int16, fetch bool) Instruction {
220326
return Instruction{
221-
OpCode: StoreXAddOp(size),
327+
OpCode: AtomicOp(size, CmpXchgAtomic, fetch),
222328
Dst: dst,
223329
Src: src,
330+
Offset: offset,
331+
}
332+
}
333+
334+
// AtomicStRel atomically stores src in *(dst + offset) with release semantics.
335+
func AtomicStRel(dst, src Register, size Size, offset int16) Instruction {
336+
return Instruction{
337+
OpCode: AtomicOp(size, StRelAtomic, false),
338+
Dst: dst,
339+
Src: src,
340+
Offset: offset,
341+
}
342+
}
343+
344+
// AtomicLdAcq atomically loads *(dst + offset) into src with acquire semantics.
345+
func AtomicLdAcq(dst, src Register, size Size, offset int16) Instruction {
346+
return Instruction{
347+
OpCode: AtomicOp(size, LdAcqAtomic, false),
348+
Dst: dst,
349+
Src: src,
350+
Offset: offset,
224351
}
225352
}

0 commit comments

Comments
 (0)