Skip to content

Commit 610e019

Browse files
committed
Added example using the operand.Mem
This demonstrates how to read from memory locations using operand.Mem. I think this example is valuable because I had (arbitrarily) chosen looking up a slice/string index value in assembly as a learning task. I found doing this in avo surprisingly difficult because I didn't know which type to use to express a memory location. This should hopefully clarify that for other avo beginners.
1 parent 28ebd9a commit 610e019

File tree

7 files changed

+302
-0
lines changed

7 files changed

+302
-0
lines changed

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Simple functions:
44

55
* **[add](add):** Add two numbers. The "Hello World!" of `avo`.
66
* **[sum](sum):** Sum an array of numbers.
7+
* **[mem](mem):** Load and return data, using pointers and offsets
78

89
Features:
910

examples/mem/README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# load
2+
3+
Demonstrates how to load data from a combination of pointers and offsets with `avo`.
4+
5+
The [code generator](asm.go) is as follows:
6+
7+
## Read from a []byte
8+
9+
This function takes a slice of bytes and an offset and returns the byte at that
10+
offset from the start of the slice. You can use an instance of the
11+
`operand.Mem` struct to create a pointer to a memory location. Note that the
12+
`Base` is a register containing the address of the slice `b`'s data and the
13+
`Index` is a register containing the offset value. We indicate the `Scale` here
14+
to calculate the full size-in-bytes of the offset, since the data here is
15+
single bytes the `Scale` is 1. Once this is done we use `MOVB` to read from the
16+
memory location into an 8 bit register. We use `Store(...)` and
17+
`ReturnIndex(0)` to write the return value.
18+
19+
[embedmd]:# (asm.go go /.*TEXT.*ByteFromSlice/ /RET.*/)
20+
```go
21+
TEXT("ByteFromSlice", NOSPLIT, "func(b []byte, offset int) byte")
22+
bytesBase := Load(Param("b").Base(), GP64())
23+
bytesOffset := Load(Param("offset"), GP64())
24+
25+
bytesData := Mem{Base: bytesBase, Index: bytesOffset, Scale: 1}
26+
bytesByte := GP8()
27+
MOVB(bytesData, bytesByte)
28+
29+
Store(bytesByte, ReturnIndex(0))
30+
RET()
31+
```
32+
33+
[embedmd]:# (mem.s)
34+
```s
35+
// func ByteFromSlice(b []byte, offset int) byte
36+
TEXT ·ByteFromSlice(SB), NOSPLIT, $0-33
37+
MOVQ b_base+0(FP), AX
38+
MOVQ offset+24(FP), CX
39+
MOVB (AX)(CX*1), AL
40+
MOVB AL, ret+32(FP)
41+
RET
42+
```
43+
44+
## Read from a string
45+
46+
This function takes a string and an offset and returns the byte at that offset
47+
from the start of the string. We use an instance of `operand.Mem` just as
48+
above. Because the string is interpreted as being made of bytes here the
49+
`Scale` is still 1.
50+
51+
[embedmd]:# (asm.go go /.*TEXT.*ByteFromSlice/ /RET.*/)
52+
```go
53+
TEXT("ByteFromString", NOSPLIT, "func(s string, offset int) byte")
54+
stringBase := Load(Param("s").Base(), GP64())
55+
stringOffset := Load(Param("offset"), GP64())
56+
57+
stringData := Mem{Base: stringBase, Index: stringOffset, Scale: 1}
58+
stringByte := GP8()
59+
MOVB(stringData, stringByte)
60+
61+
Store(stringByte, ReturnIndex(0))
62+
RET()
63+
```
64+
65+
[embedmd]:# (mem.s)
66+
```s
67+
// func ByteFromString(s string, offset int) byte
68+
TEXT ·ByteFromString(SB), NOSPLIT, $0-25
69+
MOVQ s_base+0(FP), AX
70+
MOVQ offset+16(FP), CX
71+
MOVB (AX)(CX*1), AL
72+
MOVB AL, ret+24(FP)
73+
RET
74+
```
75+
76+
## Read from an int32 slice
77+
78+
This function takes a slice of int32 and an offset and returns the int32 at
79+
that offset from the start of the slice. We use an instance of `operand.Mem`
80+
just as above. Because the slice contains int32 sized data the `Scale` is 4, as
81+
each int32 is 4 bytes wide.
82+
83+
[embedmd]:# (asm.go go /.*TEXT.*ByteFromSlice/ /RET.*/)
84+
```go
85+
TEXT("Int32FromSlice", NOSPLIT, "func(i []int32, offset int) int32")
86+
int32Base := Load(Param("i").Base(), GP64())
87+
int32Offset := Load(Param("offset"), GP64())
88+
89+
int32Data := Mem{Base: int32Base, Index: int32Offset, Scale: 4}
90+
int32Byte := GP32()
91+
MOVL(int32Data, int32Byte)
92+
93+
Store(int32Byte, ReturnIndex(0))
94+
RET()
95+
```
96+
97+
[embedmd]:# (mem.s)
98+
```s
99+
// func Int32FromSlice(i []int32, offset int) int32
100+
TEXT ·Int32FromSlice(SB), NOSPLIT, $0-36
101+
MOVQ i_base+0(FP), AX
102+
MOVQ offset+24(FP), CX
103+
MOVL (AX)(CX*4), AX
104+
MOVL AX, ret+32(FP)
105+
RET
106+
```
107+
108+
## Read from an int64 slice
109+
110+
This function takes a slice of int64 and an offset and returns the int64 at
111+
that offset from the start of the slice. We use an instance of `operand.Mem`
112+
just as above. Because the slice contains int64 sized data the `Scale` is 8, as
113+
each int64 is 8 bytes wide.
114+
115+
[embedmd]:# (asm.go go /.*TEXT.*ByteFromSlice/ /RET.*/)
116+
```go
117+
TEXT("Int64FromSlice", NOSPLIT, "func(i []int64, offset int) int64")
118+
int64Base := Load(Param("i").Base(), GP64())
119+
int64Offset := Load(Param("offset"), GP64())
120+
121+
int64Data := Mem{Base: int64Base, Index: int64Offset, Scale: 4}
122+
int64Byte := GP64()
123+
MOVQ(int64Data, int64Byte)
124+
125+
Store(int64Byte, ReturnIndex(0))
126+
RET()
127+
```
128+
129+
[embedmd]:# (mem.s)
130+
```s
131+
// func Int64FromSlice(i []int64, offset int) int64
132+
TEXT ·Int64FromSlice(SB), NOSPLIT, $0-40
133+
MOVQ i_base+0(FP), AX
134+
MOVQ offset+24(FP), CX
135+
MOVQ (AX)(CX*4), AX
136+
MOVQ AX, ret+32(FP)
137+
RET
138+
```

examples/mem/asm.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//go:build ignore
2+
3+
package main
4+
5+
import (
6+
. "github.com/mmcloughlin/avo/build"
7+
. "github.com/mmcloughlin/avo/operand"
8+
)
9+
10+
func main() {
11+
TEXT("ByteFromSlice", NOSPLIT, "func(b []byte, offset int) byte")
12+
Doc("Takes a slice of bytes and an offset and returns the byte at that offset, performs no bounds checking - very unsafe")
13+
bytesBase := Load(Param("b").Base(), GP64())
14+
bytesOffset := Load(Param("offset"), GP64())
15+
16+
bytesData := Mem{Base: bytesBase, Index: bytesOffset, Scale: 1}
17+
bytesByte := GP8()
18+
MOVB(bytesData, bytesByte)
19+
20+
Store(bytesByte, ReturnIndex(0))
21+
RET()
22+
23+
TEXT("ByteFromString", NOSPLIT, "func(s string, offset int) byte")
24+
Doc("Takes a string and an offset and returns the byte at that offset, performs no bounds checking - very unsafe")
25+
stringBase := Load(Param("s").Base(), GP64())
26+
stringOffset := Load(Param("offset"), GP64())
27+
28+
stringData := Mem{Base: stringBase, Index: stringOffset, Scale: 1}
29+
stringByte := GP8()
30+
MOVB(stringData, stringByte)
31+
32+
Store(stringByte, ReturnIndex(0))
33+
RET()
34+
35+
TEXT("Int32FromSlice", NOSPLIT, "func(i []int32, offset int) int32")
36+
Doc("Takes a slice of int32 and an offset and returns the int32 at that offset, performs no bounds checking - very unsafe")
37+
int32Base := Load(Param("i").Base(), GP64())
38+
int32Offset := Load(Param("offset"), GP64())
39+
40+
int32Data := Mem{Base: int32Base, Index: int32Offset, Scale: 4}
41+
int32Byte := GP32()
42+
MOVL(int32Data, int32Byte)
43+
44+
Store(int32Byte, ReturnIndex(0))
45+
RET()
46+
47+
TEXT("Int64FromSlice", NOSPLIT, "func(i []int64, offset int) int64")
48+
Doc("Takes a slice of int64 and an offset and returns the int64 at that offset, performs no bounds checking - very unsafe")
49+
int64Base := Load(Param("i").Base(), GP64())
50+
int64Offset := Load(Param("offset"), GP64())
51+
52+
int64Data := Mem{Base: int64Base, Index: int64Offset, Scale: 8}
53+
int64Byte := GP64()
54+
MOVQ(int64Data, int64Byte)
55+
56+
Store(int64Byte, ReturnIndex(0))
57+
RET()
58+
59+
Generate()
60+
}

examples/mem/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package mem demonstrates the use of `operand.Mem` in avo.
2+
package mem

examples/mem/mem.s

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Code generated by command: go run asm.go -out mem.s -stubs stub.go. DO NOT EDIT.
2+
3+
#include "textflag.h"
4+
5+
// func ByteFromSlice(b []byte, offset int) byte
6+
TEXT ·ByteFromSlice(SB), NOSPLIT, $0-33
7+
MOVQ b_base+0(FP), AX
8+
MOVQ offset+24(FP), CX
9+
MOVB (AX)(CX*1), AL
10+
MOVB AL, ret+32(FP)
11+
RET
12+
13+
// func ByteFromString(s string, offset int) byte
14+
TEXT ·ByteFromString(SB), NOSPLIT, $0-25
15+
MOVQ s_base+0(FP), AX
16+
MOVQ offset+16(FP), CX
17+
MOVB (AX)(CX*1), AL
18+
MOVB AL, ret+24(FP)
19+
RET
20+
21+
// func Int32FromSlice(i []int32, offset int) int32
22+
TEXT ·Int32FromSlice(SB), NOSPLIT, $0-36
23+
MOVQ i_base+0(FP), AX
24+
MOVQ offset+24(FP), CX
25+
MOVL (AX)(CX*4), AX
26+
MOVL AX, ret+32(FP)
27+
RET
28+
29+
// func Int64FromSlice(i []int64, offset int) int64
30+
TEXT ·Int64FromSlice(SB), NOSPLIT, $0-40
31+
MOVQ i_base+0(FP), AX
32+
MOVQ offset+24(FP), CX
33+
MOVQ (AX)(CX*8), AX
34+
MOVQ AX, ret+32(FP)
35+
RET

examples/mem/mem_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package mem
2+
3+
import (
4+
"testing"
5+
)
6+
7+
//go:generate go run asm.go -out mem.s -stubs stub.go
8+
9+
func TestByteFromSlice(t *testing.T) {
10+
slice := []byte{1, 2, 3, 4}
11+
for i := range slice {
12+
val := slice[i]
13+
asmVal := ByteFromSlice(slice, i)
14+
if val != asmVal {
15+
t.Fatalf("ByteFromSlice[%d] got %d, should be %d", i, asmVal, val)
16+
}
17+
}
18+
}
19+
20+
func TestByteFromString(t *testing.T) {
21+
str := "abcdefg"
22+
for i := range str {
23+
val := str[i]
24+
asmVal := ByteFromString(str, i)
25+
if val != asmVal {
26+
t.Fatalf("ByteFromString[%d] got %d, should be %d", i, asmVal, val)
27+
}
28+
}
29+
}
30+
31+
func TestFromInt32(t *testing.T) {
32+
slice := []int32{1, 2, 3, 4}
33+
for i := range slice {
34+
val := slice[i]
35+
asmVal := Int32FromSlice(slice, i)
36+
if val != asmVal {
37+
t.Fatalf("ByteFromString[%d] got %d, should be %d", i, asmVal, val)
38+
}
39+
}
40+
}
41+
42+
func TestFromInt64(t *testing.T) {
43+
slice := []int64{1, 2, 3, 4}
44+
for i := range slice {
45+
val := slice[i]
46+
asmVal := Int64FromSlice(slice, i)
47+
if val != asmVal {
48+
t.Fatalf("ByteFromString[%d] got %d, should be %d", i, asmVal, val)
49+
}
50+
}
51+
}

examples/mem/stub.go

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

0 commit comments

Comments
 (0)