Compare commits
92 Commits
467fbd9725
...
main
Author | SHA1 | Date | |
---|---|---|---|
9d6356e8a9
|
|||
aae3a552bb
|
|||
9107a06df5
|
|||
a023b058f8
|
|||
df725a2b23
|
|||
53ecf40229
|
|||
392b35423f
|
|||
80aa833a9b
|
|||
19a8154e06
|
|||
6483573923
|
|||
43a006e4af
|
|||
a9d783c675
|
|||
08ea91f46c
|
|||
cac2bad2fe
|
|||
543558a02b
|
|||
ba34637dc1
|
|||
863fb7de5a
|
|||
9302eaef2f
|
|||
008f097186
|
|||
0bc52fb673
|
|||
33b91e7bf4
|
|||
ae928156c5
|
|||
597eb08ff6
|
|||
ac8bd65054
|
|||
c2e489f987
|
|||
9a8bf8ff64
|
|||
eb27595593
|
|||
76356b2d38
|
|||
bcb04a4cec
|
|||
b8f05c8994
|
|||
93042f81e7
|
|||
fea4e4cbe7
|
|||
99ef02bbd6
|
|||
5f339187e6
|
|||
e123a26a1d
|
|||
72272ed9af
|
|||
1d0e49f0e3
|
|||
9410287605
|
|||
9726f1d832
|
|||
06f3af256b
|
|||
ac14ab4f7a
|
|||
5b3769a0db
|
|||
c1913d99d0
|
|||
d96c351b4b
|
|||
5a5061c5d7
|
|||
dc664dbf5a
|
|||
450e634d79
|
|||
03c8dfa34c
|
|||
e7a06f5b26
|
|||
d2ad8c8310
|
|||
325dd126f9
|
|||
7807271f43
|
|||
0d562e8002
|
|||
6c659a2b0c
|
|||
016938932f
|
|||
947a8db937
|
|||
b67f3b0977
|
|||
919d94e0f4
|
|||
0bf299d007
|
|||
0ac7fc9a85
|
|||
cb908e7b31
|
|||
7fd29ea249
|
|||
7798eca074
|
|||
5f522b519a
|
|||
2f09b96f34
|
|||
14abb8202b
|
|||
f2db223684
|
|||
e5f0123eea
|
|||
df6f7d5a57
|
|||
751614e7c0
|
|||
08660ad845
|
|||
ea233d789d
|
|||
c3054369e3
|
|||
d7f30d8319
|
|||
4428b09de2
|
|||
8ff6faa310
|
|||
e032733a92
|
|||
6c0ab72f8f
|
|||
c1427eb7f6
|
|||
be6eafddf5
|
|||
a5a8f0f503
|
|||
b67361c035
|
|||
31423ccc08
|
|||
e7afb2dab5
|
|||
efb3089211
|
|||
9f78733d5d
|
|||
ae6530aadb
|
|||
bbf2970c4e
|
|||
ebe53b3b50
|
|||
5f46a6f14e
|
|||
91f34bc88f
|
|||
98edfb19fd
|
@ -1,8 +1,8 @@
|
||||
# q
|
||||
|
||||
A programming language that compiles down to machine code.
|
||||
A programming language that quickly compiles to machine code.
|
||||
|
||||
## Features
|
||||
## Goals
|
||||
|
||||
- Fast compilation
|
||||
- High performance
|
||||
@ -12,7 +12,7 @@ A programming language that compiles down to machine code.
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
git clone https://git.akyoto.dev/cli/q
|
||||
git clone https://git.urbach.dev/cli/q
|
||||
cd q
|
||||
go build
|
||||
```
|
||||
@ -33,7 +33,7 @@ You can take a look at the [examples](../examples).
|
||||
go run gotest.tools/gotestsum@latest
|
||||
```
|
||||
|
||||
This will run over 350 [tests](../tests) in various categories.
|
||||
This will run over 400 [tests](../tests) in various categories.
|
||||
|
||||
## Platforms
|
||||
|
||||
@ -45,16 +45,23 @@ q build examples/hello --os mac
|
||||
q build examples/hello --os windows
|
||||
```
|
||||
|
||||
Or with different architectures:
|
||||
|
||||
```shell
|
||||
q build examples/hello --arch x86
|
||||
q build examples/hello --arch arm
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
`q` is under heavy development and not ready for production yet.
|
||||
Feel free to [get in touch](https://akyoto.dev/contact) if you are interested in helping out.
|
||||
Feel free to [get in touch](https://urbach.dev/contact) if you are interested in helping out.
|
||||
|
||||
The biggest obstacle right now is the lack of funding. If you want to help out financially you can [donate towards the project](https://en.liberapay.com/akyoto).
|
||||
The biggest obstacle right now is the lack of funding. You can help by donating via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang).
|
||||
|
||||
## License
|
||||
|
||||
Please see the [license documentation](https://akyoto.dev/license).
|
||||
Please see the [license documentation](https://urbach.dev/license).
|
||||
|
||||
## Copyright
|
||||
|
||||
|
@ -49,6 +49,7 @@
|
||||
|
||||
- [x] Exclude unused functions
|
||||
- [x] Constant folding
|
||||
- [ ] SSA form
|
||||
- [ ] Constant propagation
|
||||
- [ ] Function call inlining
|
||||
- [ ] Loop unrolls
|
||||
|
@ -8,6 +8,6 @@ main() {
|
||||
buffer[2] = 'l'
|
||||
buffer[3] = 'l'
|
||||
buffer[4] = 'o'
|
||||
io.write(1, buffer)
|
||||
io.write(buffer)
|
||||
mem.free(buffer)
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import fmt
|
||||
import io
|
||||
|
||||
main() {
|
||||
@ -12,12 +13,12 @@ collatz(x int) {
|
||||
x = 3 * x + 1
|
||||
}
|
||||
|
||||
io.number(x)
|
||||
fmt.decimal(x)
|
||||
|
||||
if x == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
io.out(" ")
|
||||
io.write(" ")
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import io
|
||||
import fmt
|
||||
|
||||
main() {
|
||||
io.number(factorial(5))
|
||||
fmt.decimal(factorial(5))
|
||||
}
|
||||
|
||||
factorial(x int) -> int {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import io
|
||||
import fmt
|
||||
|
||||
main() {
|
||||
io.number(fibonacci(10))
|
||||
fmt.decimal(fibonacci(10))
|
||||
}
|
||||
|
||||
fibonacci(x int) -> int {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import fmt
|
||||
import io
|
||||
|
||||
main() {
|
||||
@ -9,10 +10,10 @@ fizzbuzz(n int) {
|
||||
|
||||
loop {
|
||||
switch {
|
||||
x % 15 == 0 { io.out("FizzBuzz") }
|
||||
x % 5 == 0 { io.out("Buzz") }
|
||||
x % 3 == 0 { io.out("Fizz") }
|
||||
_ { io.number(x) }
|
||||
x % 15 == 0 { io.write("FizzBuzz") }
|
||||
x % 5 == 0 { io.write("Buzz") }
|
||||
x % 3 == 0 { io.write("Fizz") }
|
||||
_ { fmt.decimal(x) }
|
||||
}
|
||||
|
||||
x += 1
|
||||
@ -21,6 +22,6 @@ fizzbuzz(n int) {
|
||||
return
|
||||
}
|
||||
|
||||
io.out(" ")
|
||||
io.write(" ")
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import io
|
||||
import fmt
|
||||
|
||||
main() {
|
||||
io.number(gcd(1071, 462))
|
||||
fmt.decimal(gcd(1071, 462))
|
||||
}
|
||||
|
||||
gcd(a int, b int) -> int {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import io
|
||||
|
||||
main() {
|
||||
io.out("Hello\n")
|
||||
io.write("Hello\n")
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import io
|
||||
import fmt
|
||||
|
||||
main() {
|
||||
io.number(9223372036854775807)
|
||||
fmt.decimal(9223372036854775807)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import io
|
||||
import mem
|
||||
import sys
|
||||
|
||||
struct Point {
|
||||
Point {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
@ -29,6 +29,6 @@ print(p *Point) {
|
||||
out[5] = ' '
|
||||
out[6] = '0' + p.y
|
||||
out[7] = '\n'
|
||||
sys.write(1, out, 8)
|
||||
io.write(out)
|
||||
mem.free(out)
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import fmt
|
||||
import io
|
||||
|
||||
main() {
|
||||
@ -9,12 +10,12 @@ main() {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrime(i) == 1 {
|
||||
if isPrime(i) {
|
||||
if i != 2 {
|
||||
io.out(" ")
|
||||
io.write(" ")
|
||||
}
|
||||
|
||||
io.number(i)
|
||||
fmt.decimal(i)
|
||||
}
|
||||
|
||||
i += 1
|
||||
@ -23,22 +24,22 @@ main() {
|
||||
|
||||
isPrime(x int) -> bool {
|
||||
if x == 2 {
|
||||
return 1
|
||||
return true
|
||||
}
|
||||
|
||||
if x % 2 == 0 {
|
||||
return 0
|
||||
return false
|
||||
}
|
||||
|
||||
i := 3
|
||||
|
||||
loop {
|
||||
if i * i > x {
|
||||
return 1
|
||||
return true
|
||||
}
|
||||
|
||||
if x % i == 0 {
|
||||
return 0
|
||||
return false
|
||||
}
|
||||
|
||||
i += 2
|
||||
|
13
examples/readfile/readfile.q
Normal file
13
examples/readfile/readfile.q
Normal file
@ -0,0 +1,13 @@
|
||||
import fs
|
||||
import sys
|
||||
|
||||
main() {
|
||||
show("/proc/sys/kernel/ostype")
|
||||
show("/proc/sys/kernel/osrelease")
|
||||
show("/proc/sys/kernel/arch")
|
||||
}
|
||||
|
||||
show(path []byte) {
|
||||
contents, length := fs.readFile(path)
|
||||
sys.write(1, contents, length)
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
// Open server and client in 2 terminals:
|
||||
// [1] q run examples/server
|
||||
// [2] curl http://127.0.0.1:8080
|
||||
import io
|
||||
import net
|
||||
import sys
|
||||
@ -9,30 +6,30 @@ main() {
|
||||
socket := sys.socket(2, 1, 0)
|
||||
|
||||
if socket < 0 {
|
||||
io.error("socket error\n")
|
||||
io.write("socket error\n")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if net.bind(socket, 8080) != 0 {
|
||||
io.error("bind error\n")
|
||||
io.write("bind error\n")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if sys.listen(socket, 128) != 0 {
|
||||
io.error("listen error\n")
|
||||
io.write("listen error\n")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
io.out("listening...\n")
|
||||
io.write("listening...\n")
|
||||
|
||||
loop {
|
||||
conn := sys.accept(socket, 0, 0)
|
||||
|
||||
if conn >= 0 {
|
||||
io.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n")
|
||||
io.writeTo(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n")
|
||||
sys.close(conn)
|
||||
} else {
|
||||
io.error("accept error\n")
|
||||
io.write("accept error\n")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
const idtype {
|
||||
const {
|
||||
idtype {
|
||||
pid 1
|
||||
}
|
||||
|
||||
const state {
|
||||
sig {
|
||||
chld 17
|
||||
}
|
||||
|
||||
state {
|
||||
exited 0x4
|
||||
}
|
||||
}
|
@ -7,15 +7,15 @@ main() {
|
||||
command := mem.alloc(length)
|
||||
|
||||
loop {
|
||||
io.out("λ ")
|
||||
n := io.in(command)
|
||||
io.write("λ ")
|
||||
n := io.read(command)
|
||||
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
command[n-1] = 0
|
||||
pid := sys.fork()
|
||||
pid := sys.clone(sig.chld, 0, 0, 0, 0)
|
||||
|
||||
if pid == 0 {
|
||||
sys.execve(command, 0, 0)
|
||||
|
@ -10,6 +10,6 @@ main() {
|
||||
}
|
||||
|
||||
work() {
|
||||
io.out("[ ] start\n")
|
||||
io.out("[x] end\n")
|
||||
io.write("[ ] start\n")
|
||||
io.write("[x] end\n")
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
extern user32 {
|
||||
MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int
|
||||
}
|
||||
|
||||
main() {
|
||||
title := "Title."
|
||||
text := "Hi!"
|
||||
user32.MessageBoxA(0, text, title, 0x240040)
|
||||
}
|
||||
|
||||
extern {
|
||||
user32 {
|
||||
MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int
|
||||
}
|
||||
}
|
8
go.mod
8
go.mod
@ -1,10 +1,10 @@
|
||||
module git.akyoto.dev/cli/q
|
||||
module git.urbach.dev/cli/q
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
git.akyoto.dev/go/assert v0.1.3
|
||||
git.akyoto.dev/go/color v0.1.3
|
||||
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf
|
||||
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.30.0 // indirect
|
||||
require golang.org/x/sys v0.32.0 // indirect
|
||||
|
12
go.sum
12
go.sum
@ -1,6 +1,6 @@
|
||||
git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8=
|
||||
git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM=
|
||||
git.akyoto.dev/go/color v0.1.3 h1:kqOVYaPJJDHi8qEwTZkZecQaUENBToaLmiiV1Gg++GM=
|
||||
git.akyoto.dev/go/color v0.1.3/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE=
|
||||
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo=
|
||||
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A=
|
||||
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
|
12
lib/core/core_linux_arm.q
Normal file
12
lib/core/core_linux_arm.q
Normal file
@ -0,0 +1,12 @@
|
||||
init() {
|
||||
main.main()
|
||||
exit()
|
||||
}
|
||||
|
||||
exit() {
|
||||
syscall(93, 0)
|
||||
}
|
||||
|
||||
crash() {
|
||||
syscall(93, 1)
|
||||
}
|
@ -6,3 +6,7 @@ init() {
|
||||
exit() {
|
||||
syscall(60, 0)
|
||||
}
|
||||
|
||||
crash() {
|
||||
syscall(60, 1)
|
||||
}
|
@ -6,3 +6,7 @@ init() {
|
||||
exit() {
|
||||
syscall(0x2000001, 0)
|
||||
}
|
||||
|
||||
crash() {
|
||||
syscall(0x2000001, 1)
|
||||
}
|
@ -9,12 +9,20 @@ exit() {
|
||||
kernel32.ExitProcess(0)
|
||||
}
|
||||
|
||||
const cp {
|
||||
utf8 65001
|
||||
crash() {
|
||||
kernel32.ExitProcess(1)
|
||||
}
|
||||
|
||||
extern kernel32 {
|
||||
const {
|
||||
cp {
|
||||
utf8 65001
|
||||
}
|
||||
}
|
||||
|
||||
extern {
|
||||
kernel32 {
|
||||
SetConsoleCP(cp uint)
|
||||
SetConsoleOutputCP(cp uint)
|
||||
ExitProcess(code uint)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import mem
|
||||
import sys
|
||||
|
||||
number(x int) {
|
||||
decimal(x int) {
|
||||
buffer := mem.alloc(20)
|
||||
address, count := itoa(x, buffer)
|
||||
sys.write(1, address, count)
|
27
lib/fs/fs.q
Normal file
27
lib/fs/fs.q
Normal file
@ -0,0 +1,27 @@
|
||||
import mem
|
||||
import sys
|
||||
|
||||
readFile(path []byte) -> (*byte, int) {
|
||||
stat := new(sys.file_stat)
|
||||
|
||||
if sys.stat(path, stat) < 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
size := stat.st_size
|
||||
|
||||
if size < 512 {
|
||||
size = 512
|
||||
}
|
||||
|
||||
file := sys.open(path, 0, 0)
|
||||
|
||||
if file < 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
contents := mem.alloc(size)
|
||||
length := sys.read(file, contents, size)
|
||||
sys.close(file)
|
||||
return contents, length
|
||||
}
|
12
lib/io/io.q
12
lib/io/io.q
@ -1,9 +1,7 @@
|
||||
import sys
|
||||
|
||||
read(fd int, buffer []byte) -> int {
|
||||
return sys.read(fd, buffer, len(buffer))
|
||||
const {
|
||||
std {
|
||||
in 0
|
||||
out 1
|
||||
err 2
|
||||
}
|
||||
|
||||
write(fd int, buffer []byte) -> int {
|
||||
return sys.write(fd, buffer, len(buffer))
|
||||
}
|
9
lib/io/read.q
Normal file
9
lib/io/read.q
Normal file
@ -0,0 +1,9 @@
|
||||
import sys
|
||||
|
||||
read(buffer []byte) -> int {
|
||||
return sys.read(std.in, buffer, len(buffer))
|
||||
}
|
||||
|
||||
readFrom(fd int, buffer []byte) -> int {
|
||||
return sys.read(fd, buffer, len(buffer))
|
||||
}
|
19
lib/io/std.q
19
lib/io/std.q
@ -1,19 +0,0 @@
|
||||
import sys
|
||||
|
||||
in(buffer []byte) -> int {
|
||||
return sys.read(std.in, buffer, len(buffer))
|
||||
}
|
||||
|
||||
out(buffer []byte) -> int {
|
||||
return sys.write(std.out, buffer, len(buffer))
|
||||
}
|
||||
|
||||
error(buffer []byte) -> int {
|
||||
return sys.write(std.err, buffer, len(buffer))
|
||||
}
|
||||
|
||||
const std {
|
||||
in 0
|
||||
out 1
|
||||
err 2
|
||||
}
|
9
lib/io/write.q
Normal file
9
lib/io/write.q
Normal file
@ -0,0 +1,9 @@
|
||||
import sys
|
||||
|
||||
write(buffer []byte) -> int {
|
||||
return sys.write(std.out, buffer, len(buffer))
|
||||
}
|
||||
|
||||
writeTo(fd int, buffer []byte) -> int {
|
||||
return sys.write(fd, buffer, len(buffer))
|
||||
}
|
11
lib/math/math.q
Normal file
11
lib/math/math.q
Normal file
@ -0,0 +1,11 @@
|
||||
align2(x uint64) -> uint64 {
|
||||
x -= 1
|
||||
x |= x >> 1
|
||||
x |= x >> 2
|
||||
x |= x >> 4
|
||||
x |= x >> 8
|
||||
x |= x >> 16
|
||||
x |= x >> 32
|
||||
x += 1
|
||||
return x
|
||||
}
|
@ -7,6 +7,6 @@ alloc(length int) -> []byte {
|
||||
return x
|
||||
}
|
||||
|
||||
store(x, 8, length)
|
||||
[x] = length
|
||||
return x + 8
|
||||
}
|
@ -1,7 +1,3 @@
|
||||
extern kernel32 {
|
||||
VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any
|
||||
}
|
||||
|
||||
alloc(length int) -> []byte {
|
||||
x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite)
|
||||
|
||||
@ -9,6 +5,12 @@ alloc(length int) -> []byte {
|
||||
return x
|
||||
}
|
||||
|
||||
store(x, 8, length)
|
||||
[x] = length
|
||||
return x + 8
|
||||
}
|
||||
|
||||
extern {
|
||||
kernel32 {
|
||||
VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
const prot {
|
||||
const {
|
||||
prot {
|
||||
read 0x1
|
||||
write 0x2
|
||||
}
|
||||
|
||||
const map {
|
||||
map {
|
||||
private 0x02
|
||||
anonymous 0x20
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
const prot {
|
||||
const {
|
||||
prot {
|
||||
read 0x1
|
||||
write 0x2
|
||||
}
|
||||
|
||||
const map {
|
||||
map {
|
||||
private 0x02
|
||||
anonymous 0x1000
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
const page {
|
||||
const {
|
||||
page {
|
||||
readwrite 0x0004
|
||||
}
|
||||
|
||||
const mem {
|
||||
mem {
|
||||
commit 0x1000
|
||||
reserve 0x2000
|
||||
decommit 0x4000
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import sys
|
||||
|
||||
free(address []any) -> int {
|
||||
return sys.munmap(address-8, len(address)+8)
|
||||
free(address []any) {
|
||||
sys.munmap(address-8, len(address)+8)
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
extern kernel32 {
|
||||
VirtualFree(address *any, size uint, type uint32) -> bool
|
||||
free(address []any) {
|
||||
kernel32.VirtualFree(address-8, len(address)+8, mem.decommit)
|
||||
}
|
||||
|
||||
free(address []any) -> int {
|
||||
return kernel32.VirtualFree(address-8, len(address)+8, mem.decommit)
|
||||
extern {
|
||||
kernel32 {
|
||||
VirtualFree(address *any, size uint, type uint32) -> bool
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import sys
|
||||
|
||||
bind(socket int, port int) -> int {
|
||||
bind(socket int, port uint16) -> int {
|
||||
addr := new(sys.sockaddr_in)
|
||||
addr.sin_family = 2
|
||||
addr.sin_port = htons(port)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import sys
|
||||
|
||||
bind(socket int, port int) -> int {
|
||||
bind(socket int, port uint16) -> int {
|
||||
addr := new(sys.sockaddr_in_bsd)
|
||||
addr.sin_family = 2
|
||||
addr.sin_port = htons(port)
|
||||
|
@ -1,11 +1,34 @@
|
||||
struct sockaddr_in {
|
||||
sockaddr_in {
|
||||
sin_family int16
|
||||
sin_port int16
|
||||
sin_port uint16
|
||||
sin_addr int64
|
||||
sin_zero int64
|
||||
}
|
||||
|
||||
struct timespec {
|
||||
file_stat {
|
||||
st_dev uint64
|
||||
st_ino uint64
|
||||
st_nlink uint64
|
||||
st_mode uint32
|
||||
st_uid uint32
|
||||
st_gid uint32
|
||||
_ uint32
|
||||
st_rdev uint64
|
||||
st_size int64
|
||||
st_blksize int64
|
||||
st_blocks int64
|
||||
st_atime int64
|
||||
st_atime_nsec int64
|
||||
st_mtime int64
|
||||
st_mtime_nsec int64
|
||||
st_ctime int64
|
||||
st_ctime_nsec int64
|
||||
_ int64
|
||||
_ int64
|
||||
_ int64
|
||||
}
|
||||
|
||||
timespec {
|
||||
seconds int64
|
||||
nanoseconds int64
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
struct sockaddr_in_bsd {
|
||||
sockaddr_in_bsd {
|
||||
sin_len int8
|
||||
sin_family int8
|
||||
sin_port int16
|
||||
sin_port uint16
|
||||
sin_addr int64
|
||||
sin_zero int64
|
||||
}
|
@ -1,91 +1,91 @@
|
||||
read(fd int, buffer *byte, length int) -> int {
|
||||
return syscall(0, fd, buffer, length)
|
||||
return syscall(n.read, fd, buffer, length)
|
||||
}
|
||||
|
||||
write(fd int, buffer *byte, length int) -> int {
|
||||
return syscall(1, fd, buffer, length)
|
||||
}
|
||||
|
||||
open(path *any, flags int, mode int) -> int {
|
||||
return syscall(2, path, flags, mode)
|
||||
}
|
||||
|
||||
close(fd int) -> int {
|
||||
return syscall(3, fd)
|
||||
return syscall(n.write, fd, buffer, length)
|
||||
}
|
||||
|
||||
mmap(address int, length uint, protection int, flags int) -> *any {
|
||||
return syscall(9, address, length, protection, flags)
|
||||
return syscall(n.mmap, address, length, protection, flags)
|
||||
}
|
||||
|
||||
munmap(address *any, length uint) -> int {
|
||||
return syscall(11, address, length)
|
||||
return syscall(n.munmap, address, length)
|
||||
}
|
||||
|
||||
open(path *any, flags int, mode int) -> int {
|
||||
return syscall(n.openat, -100, path, flags, mode)
|
||||
}
|
||||
|
||||
close(fd int) -> int {
|
||||
return syscall(n.close, fd)
|
||||
}
|
||||
|
||||
stat(path *any, stat *file_stat) -> int {
|
||||
return syscall(n.newfstatat, -100, path, stat, 0)
|
||||
}
|
||||
|
||||
clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int {
|
||||
return syscall(56, flags, stack, parent, child, tls)
|
||||
}
|
||||
|
||||
fork() -> int {
|
||||
return syscall(57)
|
||||
return syscall(n.clone, flags, stack, parent, child, tls)
|
||||
}
|
||||
|
||||
execve(path *any, argv *any, envp *any) -> int {
|
||||
return syscall(59, path, argv, envp)
|
||||
return syscall(n.execve, path, argv, envp)
|
||||
}
|
||||
|
||||
exit(status int) {
|
||||
syscall(60, status)
|
||||
syscall(n.exit, status)
|
||||
}
|
||||
|
||||
waitid(type int, id int, info *any, options int) -> int {
|
||||
return syscall(247, type, id, info, options)
|
||||
return syscall(n.waitid, type, id, info, options)
|
||||
}
|
||||
|
||||
socket(family int, type int, protocol int) -> int {
|
||||
return syscall(41, family, type, protocol)
|
||||
return syscall(n.socket, family, type, protocol)
|
||||
}
|
||||
|
||||
accept(fd int, address *any, length int) -> int {
|
||||
return syscall(43, fd, address, length)
|
||||
return syscall(n.accept, fd, address, length)
|
||||
}
|
||||
|
||||
bind(fd int, address *sockaddr_in, length int) -> int {
|
||||
return syscall(49, fd, address, length)
|
||||
return syscall(n.bind, fd, address, length)
|
||||
}
|
||||
|
||||
listen(fd int, backlog int) -> int {
|
||||
return syscall(50, fd, backlog)
|
||||
return syscall(n.listen, fd, backlog)
|
||||
}
|
||||
|
||||
setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int {
|
||||
return syscall(54, fd, level, optname, optval, optlen)
|
||||
return syscall(n.setsockopt, fd, level, optname, optval, optlen)
|
||||
}
|
||||
|
||||
getcwd(buffer *any, length int) -> int {
|
||||
return syscall(79, buffer, length)
|
||||
return syscall(n.getcwd, buffer, length)
|
||||
}
|
||||
|
||||
chdir(path *any) -> int {
|
||||
return syscall(80, path)
|
||||
return syscall(n.chdir, path)
|
||||
}
|
||||
|
||||
rename(old *any, new *any) -> int {
|
||||
return syscall(82, old, new)
|
||||
return syscall(n.renameat, -100, old, -100, new)
|
||||
}
|
||||
|
||||
mkdir(path *any, mode int) -> int {
|
||||
return syscall(83, path, mode)
|
||||
return syscall(n.mkdirat, -100, path, mode)
|
||||
}
|
||||
|
||||
rmdir(path *any) -> int {
|
||||
return syscall(84, path)
|
||||
return syscall(n.unlinkat, -100, path, 0x200)
|
||||
}
|
||||
|
||||
unlink(file *any) -> int {
|
||||
return syscall(87, file)
|
||||
return syscall(n.unlinkat, -100, file, 0)
|
||||
}
|
||||
|
||||
nanosleep(duration *timespec) -> int {
|
||||
return syscall(35, duration, 0)
|
||||
return syscall(n.nanosleep, duration, 0)
|
||||
}
|
26
lib/sys/sys_linux_arm.q
Normal file
26
lib/sys/sys_linux_arm.q
Normal file
@ -0,0 +1,26 @@
|
||||
const {
|
||||
n {
|
||||
read 63
|
||||
write 64
|
||||
mmap 222
|
||||
munmap 215
|
||||
openat 56
|
||||
close 57
|
||||
newfstatat 79
|
||||
clone 220
|
||||
execve 221
|
||||
exit 93
|
||||
waitid 95
|
||||
socket 198
|
||||
accept 202
|
||||
bind 200
|
||||
listen 201
|
||||
setsockopt 208
|
||||
getcwd 17
|
||||
chdir 49
|
||||
mkdirat 34
|
||||
unlinkat 35
|
||||
renameat 38
|
||||
nanosleep 101
|
||||
}
|
||||
}
|
27
lib/sys/sys_linux_x86.q
Normal file
27
lib/sys/sys_linux_x86.q
Normal file
@ -0,0 +1,27 @@
|
||||
const {
|
||||
n {
|
||||
read 0
|
||||
write 1
|
||||
mmap 9
|
||||
munmap 11
|
||||
openat 257
|
||||
close 3
|
||||
newfstatat 262
|
||||
clone 56
|
||||
execve 59
|
||||
exit 60
|
||||
waitid 247
|
||||
socket 41
|
||||
accept 43
|
||||
bind 49
|
||||
listen 50
|
||||
setsockopt 54
|
||||
getcwd 79
|
||||
chdir 80
|
||||
mkdirat 258
|
||||
unlinkat 263
|
||||
renameat 264
|
||||
nanosleep 35
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
read(fd int64, buffer *byte, length int64) -> int64 {
|
||||
read(fd int, buffer *byte, length int) -> int {
|
||||
fd = kernel32.GetStdHandle(-10 - fd)
|
||||
kernel32.ReadConsole(fd, buffer, length, 0)
|
||||
kernel32.ReadConsole(fd, buffer, uint32(length), 0)
|
||||
return length
|
||||
}
|
||||
|
||||
write(fd int64, buffer *byte, length int64) -> int64 {
|
||||
write(fd int, buffer *byte, length int) -> int {
|
||||
fd = kernel32.GetStdHandle(-10 - fd)
|
||||
kernel32.WriteConsoleA(fd, buffer, length, 0)
|
||||
kernel32.WriteConsoleA(fd, buffer, uint32(length), 0)
|
||||
return length
|
||||
}
|
||||
|
||||
extern kernel32 {
|
||||
extern {
|
||||
kernel32 {
|
||||
GetStdHandle(handle int64) -> int64
|
||||
ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool
|
||||
WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool
|
||||
}
|
||||
}
|
@ -1,7 +1,17 @@
|
||||
import core
|
||||
import sys
|
||||
|
||||
const clone {
|
||||
create(func *any) -> int {
|
||||
stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100)
|
||||
stack += 4096 - 8
|
||||
[stack] = core.exit
|
||||
stack -= 8
|
||||
[stack] = func
|
||||
return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0)
|
||||
}
|
||||
|
||||
const {
|
||||
clone {
|
||||
vm 0x100
|
||||
fs 0x200
|
||||
files 0x400
|
||||
@ -10,14 +20,4 @@ const clone {
|
||||
thread 0x10000
|
||||
io 0x80000000
|
||||
}
|
||||
|
||||
create(func *any) -> int {
|
||||
size := 4096
|
||||
stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000)
|
||||
stack += size
|
||||
stack -= 8
|
||||
store(stack, 8, core.exit)
|
||||
stack -= 8
|
||||
store(stack, 8, func)
|
||||
return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0)
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
extern kernel32 {
|
||||
CreateThread(attributes int, stackSize int, address *any, parameter int) -> int
|
||||
}
|
||||
|
||||
create(func *any) -> int {
|
||||
return kernel32.CreateThread(0, 4096, func, 0)
|
||||
}
|
||||
|
||||
extern {
|
||||
kernel32 {
|
||||
CreateThread(attributes int, stackSize int, address *any, parameter int) -> int
|
||||
}
|
||||
}
|
2
main.go
2
main.go
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/cli"
|
||||
"git.urbach.dev/cli/q/src/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
38
src/arm/Add.go
Normal file
38
src/arm/Add.go
Normal file
@ -0,0 +1,38 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// AddRegisterNumber adds a number to a register.
|
||||
func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) {
|
||||
return addRegisterNumber(destination, source, number, 0)
|
||||
}
|
||||
|
||||
// AddRegisterRegister adds a register to a register.
|
||||
func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
|
||||
return addRegisterRegister(destination, source, operand, 0)
|
||||
}
|
||||
|
||||
// addRegisterNumber adds the register and optionally updates the condition flags based on the result.
|
||||
func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) {
|
||||
shift := uint32(0)
|
||||
|
||||
if number > mask12 {
|
||||
if number&mask12 != 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
shift = 1
|
||||
number >>= 12
|
||||
|
||||
if number > mask12 {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number), true
|
||||
}
|
||||
|
||||
// addRegisterRegister adds the registers and optionally updates the condition flags based on the result.
|
||||
func addRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 {
|
||||
return flags<<29 | 0b10001011000<<21 | reg3(destination, source, operand)
|
||||
}
|
45
src/arm/Add_test.go
Normal file
45
src/arm/Add_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestAddRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Number int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X0, 1, 0x91000400},
|
||||
{arm.X0, arm.X0, 0x1000, 0x91400400},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number)
|
||||
code, encodable := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
|
||||
assert.True(t, encodable)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, 0x8B020020},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("add %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
|
||||
code := arm.AddRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
16
src/arm/And.go
Normal file
16
src/arm/And.go
Normal file
@ -0,0 +1,16 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
)
|
||||
|
||||
// AndRegisterNumber performs a bitwise AND using a register and a number.
|
||||
func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) {
|
||||
n, immr, imms, encodable := encodeLogicalImmediate(uint(number))
|
||||
return 0b100100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable
|
||||
}
|
||||
|
||||
// AndRegisterRegister performs a bitwise AND using two registers.
|
||||
func AndRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
|
||||
return 0b10001010<<24 | reg3Imm(destination, source, operand, 0)
|
||||
}
|
49
src/arm/And_test.go
Normal file
49
src/arm/And_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestAndRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Number int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, 1, 0x92400020},
|
||||
{arm.X0, arm.X1, 2, 0x927F0020},
|
||||
{arm.X0, arm.X1, 3, 0x92400420},
|
||||
{arm.X0, arm.X1, 7, 0x92400820},
|
||||
{arm.X0, arm.X1, 16, 0x927C0020},
|
||||
{arm.X0, arm.X1, 255, 0x92401C20},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number)
|
||||
code, encodable := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
|
||||
assert.True(t, encodable)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAndRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, 0x8A020020},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("and %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
|
||||
code := arm.AndRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
8
src/arm/Call.go
Normal file
8
src/arm/Call.go
Normal file
@ -0,0 +1,8 @@
|
||||
package arm
|
||||
|
||||
// Call branches to a PC-relative offset, setting the register X30 to PC+4.
|
||||
// The offset starts from the address of this instruction and is encoded as "imm26" times 4.
|
||||
// This instruction is also known as BL (branch with link).
|
||||
func Call(offset int) uint32 {
|
||||
return uint32(0b100101<<26) | uint32(offset&mask26)
|
||||
}
|
25
src/arm/Call_test.go
Normal file
25
src/arm/Call_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestCall(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Offset int
|
||||
Code uint32
|
||||
}{
|
||||
{0, 0x94000000},
|
||||
{1, 0x94000001},
|
||||
{-1, 0x97FFFFFF},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("bl %d", pattern.Offset)
|
||||
code := arm.Call(pattern.Offset)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
17
src/arm/Compare.go
Normal file
17
src/arm/Compare.go
Normal file
@ -0,0 +1,17 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result.
|
||||
func CompareRegisterNumber(register cpu.Register, number int) (code uint32, encodable bool) {
|
||||
if number < 0 {
|
||||
return addRegisterNumber(ZR, register, -number, 1)
|
||||
}
|
||||
|
||||
return subRegisterNumber(ZR, register, number, 1)
|
||||
}
|
||||
|
||||
// CompareRegisterRegister is an alias for a subtraction that updates the conditional flags and discards the result.
|
||||
func CompareRegisterRegister(reg1 cpu.Register, reg2 cpu.Register) uint32 {
|
||||
return subRegisterRegister(ZR, reg1, reg2, 1)
|
||||
}
|
45
src/arm/Compare_test.go
Normal file
45
src/arm/Compare_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestCompareRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Source cpu.Register
|
||||
Number int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, 0, 0xF100001F},
|
||||
{arm.X0, 1, 0xF100041F},
|
||||
{arm.X0, -1, 0xB100041F},
|
||||
{arm.X0, 0x1000, 0xF140041F},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("cmp %s, %d", pattern.Source, pattern.Number)
|
||||
code, encodable := arm.CompareRegisterNumber(pattern.Source, pattern.Number)
|
||||
assert.True(t, encodable)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Left cpu.Register
|
||||
Right cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, 0xEB01001F},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("cmp %s, %s", pattern.Left, pattern.Right)
|
||||
code := arm.CompareRegisterRegister(pattern.Left, pattern.Right)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
8
src/arm/Div.go
Normal file
8
src/arm/Div.go
Normal file
@ -0,0 +1,8 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// DivSigned divides source by operand and stores the value in the destination.
|
||||
func DivSigned(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
|
||||
return 0b10011010110<<21 | 0b000011<<10 | reg3(destination, source, operand)
|
||||
}
|
26
src/arm/Div_test.go
Normal file
26
src/arm/Div_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestDivSigned(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, 0x9AC20C20},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
|
||||
code := arm.DivSigned(pattern.Destination, pattern.Source, pattern.Operand)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
41
src/arm/Jump.go
Normal file
41
src/arm/Jump.go
Normal file
@ -0,0 +1,41 @@
|
||||
package arm
|
||||
|
||||
// Jump continues program flow at the new offset.
|
||||
func Jump(offset int) uint32 {
|
||||
return 0b000101<<26 | uint32(offset&mask26)
|
||||
}
|
||||
|
||||
// JumpIfEqual jumps if the result was equal.
|
||||
func JumpIfEqual(offset int) uint32 {
|
||||
return branchCond(EQ, offset)
|
||||
}
|
||||
|
||||
// JumpIfNotEqual jumps if the result was not equal.
|
||||
func JumpIfNotEqual(offset int) uint32 {
|
||||
return branchCond(NE, offset)
|
||||
}
|
||||
|
||||
// JumpIfGreater jumps if the result was greater.
|
||||
func JumpIfGreater(offset int) uint32 {
|
||||
return branchCond(GT, offset)
|
||||
}
|
||||
|
||||
// JumpIfGreaterOrEqual jumps if the result was greater or equal.
|
||||
func JumpIfGreaterOrEqual(offset int) uint32 {
|
||||
return branchCond(GE, offset)
|
||||
}
|
||||
|
||||
// JumpIfLess jumps if the result was less.
|
||||
func JumpIfLess(offset int) uint32 {
|
||||
return branchCond(LS, offset)
|
||||
}
|
||||
|
||||
// JumpIfLessOrEqual jumps if the result was less or equal.
|
||||
func JumpIfLessOrEqual(offset int) uint32 {
|
||||
return branchCond(LE, offset)
|
||||
}
|
||||
|
||||
// branchCond performs a conditional branch to a PC-relative offset.
|
||||
func branchCond(cond condition, imm19 int) uint32 {
|
||||
return 0b01010100<<24 | uint32(imm19&mask19)<<5 | uint32(cond)
|
||||
}
|
68
src/arm/Jump_test.go
Normal file
68
src/arm/Jump_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestJump(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Type byte
|
||||
Offset int
|
||||
Code uint32
|
||||
}{
|
||||
{0, 0, 0x14000000},
|
||||
{0, 1, 0x14000001},
|
||||
{0, -1, 0x17FFFFFF},
|
||||
|
||||
{1, 0, 0x54000000},
|
||||
{1, 1, 0x54000020},
|
||||
{1, -1, 0x54FFFFE0},
|
||||
|
||||
{2, 0, 0x54000001},
|
||||
{2, 1, 0x54000021},
|
||||
{2, -1, 0x54FFFFE1},
|
||||
|
||||
{3, 0, 0x5400000C},
|
||||
{3, 1, 0x5400002C},
|
||||
{3, -1, 0x54FFFFEC},
|
||||
|
||||
{4, 0, 0x5400000A},
|
||||
{4, 1, 0x5400002A},
|
||||
{4, -1, 0x54FFFFEA},
|
||||
|
||||
{5, 0, 0x54000009},
|
||||
{5, 1, 0x54000029},
|
||||
{5, -1, 0x54FFFFE9},
|
||||
|
||||
{6, 0, 0x5400000D},
|
||||
{6, 1, 0x5400002D},
|
||||
{6, -1, 0x54FFFFED},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("b %d", pattern.Offset)
|
||||
var code uint32
|
||||
|
||||
switch pattern.Type {
|
||||
case 0:
|
||||
code = arm.Jump(pattern.Offset)
|
||||
case 1:
|
||||
code = arm.JumpIfEqual(pattern.Offset)
|
||||
case 2:
|
||||
code = arm.JumpIfNotEqual(pattern.Offset)
|
||||
case 3:
|
||||
code = arm.JumpIfGreater(pattern.Offset)
|
||||
case 4:
|
||||
code = arm.JumpIfGreaterOrEqual(pattern.Offset)
|
||||
case 5:
|
||||
code = arm.JumpIfLess(pattern.Offset)
|
||||
case 6:
|
||||
code = arm.JumpIfLessOrEqual(pattern.Offset)
|
||||
}
|
||||
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
19
src/arm/Load.go
Normal file
19
src/arm/Load.go
Normal file
@ -0,0 +1,19 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// LoadRegister loads from memory into a register.
|
||||
func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 {
|
||||
common := 1<<22 | memory(destination, base, offset)
|
||||
|
||||
switch length {
|
||||
case 1:
|
||||
return 0b00111<<27 | common
|
||||
case 2:
|
||||
return 0b01111<<27 | common
|
||||
case 4:
|
||||
return 0b10111<<27 | common
|
||||
default:
|
||||
return 0b11111<<27 | common
|
||||
}
|
||||
}
|
10
src/arm/LoadAddress.go
Normal file
10
src/arm/LoadAddress.go
Normal file
@ -0,0 +1,10 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// LoadAddress calculates the address with the PC-relative offset and writes the result to the destination register.
|
||||
func LoadAddress(destination cpu.Register, offset int) uint32 {
|
||||
hi := uint32(offset) >> 2
|
||||
lo := uint32(offset) & 0b11
|
||||
return lo<<29 | 0b10000<<24 | hi<<5 | uint32(destination)
|
||||
}
|
26
src/arm/LoadAddress_test.go
Normal file
26
src/arm/LoadAddress_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestLoadAddress(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Number int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, 56, 0x100001C0},
|
||||
{arm.X1, 80, 0x10000281},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("adr %s, %d", pattern.Destination, pattern.Number)
|
||||
code := arm.LoadAddress(pattern.Destination, pattern.Number)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
10
src/arm/LoadPair.go
Normal file
10
src/arm/LoadPair.go
Normal file
@ -0,0 +1,10 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// LoadPair calculates an address from a base register value and an immediate offset,
|
||||
// loads two 64-bit doublewords from memory, and writes them to two registers.
|
||||
// This is the post-index version of the instruction so the offset is applied to the base register after the memory access.
|
||||
func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 {
|
||||
return 0b1010100011<<22 | pair(reg1, reg2, base, offset/8)
|
||||
}
|
28
src/arm/LoadPair_test.go
Normal file
28
src/arm/LoadPair_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestLoadPair(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Reg1 cpu.Register
|
||||
Reg2 cpu.Register
|
||||
Base cpu.Register
|
||||
Offset int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.FP, arm.LR, arm.SP, 32, 0xA8C27BFD},
|
||||
{arm.FP, arm.LR, arm.SP, 16, 0xA8C17BFD},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("ldp %s, %s, [%s], #%d", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset)
|
||||
code := arm.LoadPair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
39
src/arm/Load_test.go
Normal file
39
src/arm/Load_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestLoadRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Base cpu.Register
|
||||
Offset int
|
||||
Length byte
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, -8, 1, 0x385F8020},
|
||||
{arm.X1, arm.X0, -8, 1, 0x385F8001},
|
||||
{arm.X0, arm.X1, -8, 2, 0x785F8020},
|
||||
{arm.X1, arm.X0, -8, 2, 0x785F8001},
|
||||
{arm.X0, arm.X1, -8, 4, 0xB85F8020},
|
||||
{arm.X1, arm.X0, -8, 4, 0xB85F8001},
|
||||
{arm.X0, arm.X1, -8, 8, 0xF85F8020},
|
||||
{arm.X1, arm.X0, -8, 8, 0xF85F8001},
|
||||
{arm.X2, arm.X1, -8, 8, 0xF85F8022},
|
||||
{arm.X2, arm.X1, 0, 8, 0xF8400022},
|
||||
{arm.X2, arm.X1, 8, 8, 0xF8408022},
|
||||
{arm.X2, arm.X1, -256, 8, 0xF8500022},
|
||||
{arm.X2, arm.X1, 255, 8, 0xF84FF022},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("ldur %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset, pattern.Length)
|
||||
code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Offset, pattern.Length)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
100
src/arm/Move.go
Normal file
100
src/arm/Move.go
Normal file
@ -0,0 +1,100 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/cli/q/src/sizeof"
|
||||
)
|
||||
|
||||
// MoveRegisterNumber moves a number into the given register.
|
||||
func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
instruction, encodable := MoveRegisterNumberSI(destination, number)
|
||||
|
||||
if encodable {
|
||||
return binary.LittleEndian.AppendUint32(code, instruction)
|
||||
}
|
||||
|
||||
return MoveRegisterNumberMI(code, destination, number)
|
||||
}
|
||||
|
||||
// MoveRegisterNumberMI moves a number into the given register using movz and a series of movk instructions.
|
||||
func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte {
|
||||
movz := MoveZero(destination, 0, uint16(number))
|
||||
code = binary.LittleEndian.AppendUint32(code, movz)
|
||||
num := uint64(number)
|
||||
halfword := 1
|
||||
|
||||
for {
|
||||
num >>= 16
|
||||
|
||||
if num == 0 {
|
||||
return code
|
||||
}
|
||||
|
||||
movk := MoveKeep(destination, halfword, uint16(num))
|
||||
code = binary.LittleEndian.AppendUint32(code, movk)
|
||||
halfword++
|
||||
}
|
||||
}
|
||||
|
||||
// MoveRegisterNumberSI moves a number into the given register using a single instruction.
|
||||
func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) {
|
||||
if sizeof.Signed(number) <= 2 {
|
||||
if number < 0 {
|
||||
return MoveInvertedNumber(destination, uint16(^number), 0), true
|
||||
}
|
||||
|
||||
return MoveZero(destination, 0, uint16(number)), true
|
||||
}
|
||||
|
||||
if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 {
|
||||
return MoveInvertedNumber(destination, uint16((^number)>>48), 3), true
|
||||
}
|
||||
|
||||
code, encodable := MoveBitmaskNumber(destination, number)
|
||||
|
||||
if encodable {
|
||||
return code, true
|
||||
}
|
||||
|
||||
if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 {
|
||||
return MoveInvertedNumber(destination, uint16((^number)>>32), 2), true
|
||||
}
|
||||
|
||||
if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 {
|
||||
return MoveInvertedNumber(destination, uint16((^number)>>16), 1), true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// MoveRegisterRegister copies a register to another register.
|
||||
func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 {
|
||||
if source == SP || destination == SP {
|
||||
code, _ := AddRegisterNumber(destination, source, 0)
|
||||
return code
|
||||
}
|
||||
|
||||
return OrRegisterRegister(destination, ZR, source)
|
||||
}
|
||||
|
||||
// MoveBitmaskNumber moves a bitmask immediate value to a register.
|
||||
func MoveBitmaskNumber(destination cpu.Register, number int) (uint32, bool) {
|
||||
return OrRegisterNumber(destination, ZR, number)
|
||||
}
|
||||
|
||||
// MoveInvertedNumber moves an inverted 16-bit immediate value to a register.
|
||||
func MoveInvertedNumber(destination cpu.Register, number uint16, shift uint32) uint32 {
|
||||
return 0b100100101<<23 | shift<<21 | regImm(destination, number)
|
||||
}
|
||||
|
||||
// MoveKeep moves a 16-bit integer into the given register and keeps all other bits.
|
||||
func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 {
|
||||
return 0b111100101<<23 | regImmHw(destination, halfword, number)
|
||||
}
|
||||
|
||||
// MoveZero moves a 16-bit integer into the given register and clears all other bits to zero.
|
||||
func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 {
|
||||
return 0b110100101<<23 | regImmHw(destination, halfword, number)
|
||||
}
|
121
src/arm/Move_test.go
Normal file
121
src/arm/Move_test.go
Normal file
@ -0,0 +1,121 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestMoveRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, 0xAA0103E0},
|
||||
{arm.X1, arm.X0, 0xAA0003E1},
|
||||
{arm.FP, arm.SP, 0x910003FD},
|
||||
{arm.SP, arm.FP, 0x910003BF},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mov %s, %s", pattern.Destination, pattern.Source)
|
||||
code := arm.MoveRegisterRegister(pattern.Destination, pattern.Source)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number uint64
|
||||
Code []byte
|
||||
}{
|
||||
{arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}},
|
||||
{arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}},
|
||||
{arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mov %s, 0x%X", pattern.Register, pattern.Number)
|
||||
code := arm.MoveRegisterNumber(nil, pattern.Register, int(pattern.Number))
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveRegisterNumberSI(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number uint64
|
||||
Code uint32
|
||||
}{
|
||||
// MOVZ
|
||||
{arm.X0, 0x0, 0xD2800000},
|
||||
{arm.X0, 0x1, 0xD2800020},
|
||||
{arm.X0, 0x1000, 0xD2820000},
|
||||
|
||||
// MOV (bitmask immediate)
|
||||
{arm.X0, 0x1FFFF, 0xB24043E0},
|
||||
{arm.X0, 0x7FFFFFFF, 0xB2407BE0},
|
||||
{arm.X0, 0xFFFFFFFF, 0xB2407FE0},
|
||||
{arm.X0, 0xC3FFFFFFC3FFFFFF, 0xB2026FE0},
|
||||
|
||||
// MOV (inverted wide immediate)
|
||||
{arm.X0, 0xFFFFFFFFFFFFFFFF, 0x92800000},
|
||||
{arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000},
|
||||
{arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler
|
||||
{arm.X0, 0x2FFFF, 0x92BFFFA0}, // not encodable in the GNU assembler
|
||||
|
||||
// Not encodable
|
||||
{arm.X0, 0xCAFEBABE, 0},
|
||||
{arm.X0, 0xDEADC0DE, 0},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mov %s, %d", pattern.Register, pattern.Number)
|
||||
code, encodable := arm.MoveRegisterNumberSI(pattern.Register, int(pattern.Number))
|
||||
|
||||
if pattern.Code != 0 {
|
||||
assert.True(t, encodable)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
} else {
|
||||
assert.False(t, encodable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveKeep(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number uint16
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, 0, 0xF2800000},
|
||||
{arm.X0, 1, 0xF2800020},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("movk %s, %d", pattern.Register, pattern.Number)
|
||||
code := arm.MoveKeep(pattern.Register, 0, pattern.Number)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveZero(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number uint16
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, 0, 0xD2800000},
|
||||
{arm.X0, 1, 0xD2800020},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("movz %s, %d", pattern.Register, pattern.Number)
|
||||
code := arm.MoveZero(pattern.Register, 0, pattern.Number)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
13
src/arm/Mul.go
Normal file
13
src/arm/Mul.go
Normal file
@ -0,0 +1,13 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// MulRegisterRegister multiplies `multiplicand` with `multiplier` and saves the result in `destination`
|
||||
func MulRegisterRegister(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register) uint32 {
|
||||
return 0b10011011000<<21 | reg4(destination, multiplicand, multiplier, ZR)
|
||||
}
|
||||
|
||||
// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts the product from `minuend` and saves the result in `destination`.
|
||||
func MultiplySubtract(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register, minuend cpu.Register) uint32 {
|
||||
return 0b10011011000<<21 | 1<<15 | reg4(destination, multiplicand, multiplier, minuend)
|
||||
}
|
45
src/arm/Mul_test.go
Normal file
45
src/arm/Mul_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestMulRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, 0x9B027C20},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mul %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
|
||||
code := arm.MulRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiplySubtract(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Extra cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, arm.X3, 0x9B028C20},
|
||||
{arm.X3, arm.X0, arm.X2, arm.X1, 0x9B028403},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("msub %s, %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra)
|
||||
code := arm.MultiplySubtract(pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
8
src/arm/Negate.go
Normal file
8
src/arm/Negate.go
Normal file
@ -0,0 +1,8 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// NegateRegister negates the value in the source register and writes it to the destination register.
|
||||
func NegateRegister(destination cpu.Register, source cpu.Register) uint32 {
|
||||
return 0b11001011<<24 | reg3Imm(destination, ZR, source, 0)
|
||||
}
|
26
src/arm/Negate_test.go
Normal file
26
src/arm/Negate_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestNegateRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X0, 0xCB0003E0},
|
||||
{arm.X1, arm.X1, 0xCB0103E1},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("neg %s, %s", pattern.Destination, pattern.Source)
|
||||
code := arm.NegateRegister(pattern.Destination, pattern.Source)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
6
src/arm/Nop.go
Normal file
6
src/arm/Nop.go
Normal file
@ -0,0 +1,6 @@
|
||||
package arm
|
||||
|
||||
// Nop does nothing. This can be used for alignment purposes.
|
||||
func Nop() uint32 {
|
||||
return 0xD503201F
|
||||
}
|
14
src/arm/Or.go
Normal file
14
src/arm/Or.go
Normal file
@ -0,0 +1,14 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// OrRegisterNumber performs a bitwise OR using a register and a number.
|
||||
func OrRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) {
|
||||
n, immr, imms, encodable := encodeLogicalImmediate(uint(number))
|
||||
return 0b101100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable
|
||||
}
|
||||
|
||||
// OrRegisterRegister performs a bitwise OR using two registers.
|
||||
func OrRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
|
||||
return 0b10101010<<24 | reg3(destination, source, operand)
|
||||
}
|
49
src/arm/Or_test.go
Normal file
49
src/arm/Or_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestOrRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Number int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, 1, 0xB2400020},
|
||||
{arm.X0, arm.X1, 2, 0xB27F0020},
|
||||
{arm.X0, arm.X1, 3, 0xB2400420},
|
||||
{arm.X0, arm.X1, 7, 0xB2400820},
|
||||
{arm.X0, arm.X1, 16, 0xB27C0020},
|
||||
{arm.X0, arm.X1, 255, 0xB2401C20},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("orr %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number)
|
||||
code, encodable := arm.OrRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
|
||||
assert.True(t, encodable)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, 0xAA020020},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("orr %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
|
||||
code := arm.OrRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package arm
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/cpu"
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
const (
|
||||
X0 cpu.Register = iota
|
||||
X0 cpu.Register = iota // Function arguments and return values [0-7]
|
||||
X1
|
||||
X2
|
||||
X3
|
||||
@ -11,18 +11,18 @@ const (
|
||||
X5
|
||||
X6
|
||||
X7
|
||||
X8
|
||||
X9
|
||||
X8 // Indirect result location register (used to pass a pointer to a structure return value)
|
||||
X9 // Temporary registers (caller-saved, used for general computation) [9-15]
|
||||
X10
|
||||
X11
|
||||
X12
|
||||
X13
|
||||
X14
|
||||
X15
|
||||
X16
|
||||
X16 // Intra-procedure call scratch registers [16-17]
|
||||
X17
|
||||
X18
|
||||
X19
|
||||
X18 // Platform register (reserved by the platform ABI for thread-local storage)
|
||||
X19 // Callee-saved registers (must be preserved across function calls) [19-28]
|
||||
X20
|
||||
X21
|
||||
X22
|
||||
@ -37,8 +37,23 @@ const (
|
||||
SP // Stack pointer
|
||||
)
|
||||
|
||||
const (
|
||||
ZR = SP // Zero register uses the same numerical value as SP
|
||||
TMP = X27 // Temporary register for the assembler
|
||||
TMP2 = X28 // Temporary register for the assembler
|
||||
)
|
||||
|
||||
var (
|
||||
SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5}
|
||||
InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5}
|
||||
WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7}
|
||||
WindowsOutputRegisters = []cpu.Register{X0, X1}
|
||||
|
||||
CPU = cpu.CPU{
|
||||
General: []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26},
|
||||
Input: InputRegisters,
|
||||
Output: InputRegisters,
|
||||
SyscallInput: []cpu.Register{X8, X0, X1, X2, X3, X4, X5},
|
||||
SyscallOutput: []cpu.Register{X0, X1},
|
||||
NumRegisters: 32,
|
||||
}
|
||||
)
|
||||
|
@ -3,10 +3,10 @@ package arm_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/arm"
|
||||
"git.akyoto.dev/go/assert"
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestRegisters(t *testing.T) {
|
||||
assert.NotNil(t, arm.SyscallInputRegisters)
|
||||
assert.NotContains(t, arm.CPU.General, arm.SP)
|
||||
}
|
||||
|
6
src/arm/Return.go
Normal file
6
src/arm/Return.go
Normal file
@ -0,0 +1,6 @@
|
||||
package arm
|
||||
|
||||
// Return transfers program control to the caller.
|
||||
func Return() uint32 {
|
||||
return 0xD65F03C0
|
||||
}
|
15
src/arm/Shift.go
Normal file
15
src/arm/Shift.go
Normal file
@ -0,0 +1,15 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
)
|
||||
|
||||
// ShiftLeftNumber shifts the register value a specified amount of bits to the left.
|
||||
func ShiftLeftNumber(destination cpu.Register, source cpu.Register, bits int) uint32 {
|
||||
return 0b110100110<<23 | reg2BitmaskImm(destination, source, 1, 64-bits, (^bits)&mask6)
|
||||
}
|
||||
|
||||
// ShiftRightSignedNumber shifts the signed register value a specified amount of bits to the right.
|
||||
func ShiftRightSignedNumber(destination cpu.Register, source cpu.Register, bits int) uint32 {
|
||||
return 0b100100110<<23 | reg2BitmaskImm(destination, source, 1, bits&mask6, 0b111111)
|
||||
}
|
52
src/arm/Shift_test.go
Normal file
52
src/arm/Shift_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestShiftLeftNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Bits int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X0, 0, 0xD340FC00},
|
||||
{arm.X0, arm.X0, 1, 0xD37FF800},
|
||||
{arm.X0, arm.X0, 8, 0xD378DC00},
|
||||
{arm.X0, arm.X0, 16, 0xD370BC00},
|
||||
{arm.X0, arm.X0, 63, 0xD3410000},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("%b", pattern.Code)
|
||||
t.Logf("lsl %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits)
|
||||
code := arm.ShiftLeftNumber(pattern.Destination, pattern.Source, pattern.Bits)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShiftRightSignedNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Bits int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X0, 0, 0x9340FC00},
|
||||
{arm.X0, arm.X0, 1, 0x9341FC00},
|
||||
{arm.X0, arm.X0, 8, 0x9348FC00},
|
||||
{arm.X0, arm.X0, 16, 0x9350FC00},
|
||||
{arm.X0, arm.X0, 63, 0x937FFC00},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("asr %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits)
|
||||
code := arm.ShiftRightSignedNumber(pattern.Destination, pattern.Source, pattern.Bits)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
21
src/arm/Store.go
Normal file
21
src/arm/Store.go
Normal file
@ -0,0 +1,21 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
)
|
||||
|
||||
// StoreRegister writes the contents of the register to a memory address.
|
||||
func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 {
|
||||
common := memory(source, base, offset)
|
||||
|
||||
switch length {
|
||||
case 1:
|
||||
return 0b00111<<27 | common
|
||||
case 2:
|
||||
return 0b01111<<27 | common
|
||||
case 4:
|
||||
return 0b10111<<27 | common
|
||||
default:
|
||||
return 0b11111<<27 | common
|
||||
}
|
||||
}
|
12
src/arm/StorePair.go
Normal file
12
src/arm/StorePair.go
Normal file
@ -0,0 +1,12 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
)
|
||||
|
||||
// StorePair calculates an address from a base register value and an immediate offset multiplied by 8,
|
||||
// and stores the values of two registers to the calculated address.
|
||||
// This is the pre-index version of the instruction so the offset is applied to the base register before the memory access.
|
||||
func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 {
|
||||
return 0b1010100110<<22 | pair(reg1, reg2, base, offset/8)
|
||||
}
|
28
src/arm/StorePair_test.go
Normal file
28
src/arm/StorePair_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestStorePair(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Reg1 cpu.Register
|
||||
Reg2 cpu.Register
|
||||
Base cpu.Register
|
||||
Offset int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.FP, arm.LR, arm.SP, -32, 0xA9BE7BFD},
|
||||
{arm.FP, arm.LR, arm.SP, -16, 0xA9BF7BFD},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("stp %s, %s, [%s, #%d]!", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset)
|
||||
code := arm.StorePair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
34
src/arm/Store_test.go
Normal file
34
src/arm/Store_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestStoreRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Source cpu.Register
|
||||
Base cpu.Register
|
||||
Offset int
|
||||
Length byte
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, -8, 1, 0x381F8020},
|
||||
{arm.X1, arm.X0, -8, 1, 0x381F8001},
|
||||
{arm.X0, arm.X1, -8, 2, 0x781F8020},
|
||||
{arm.X1, arm.X0, -8, 2, 0x781F8001},
|
||||
{arm.X0, arm.X1, -8, 4, 0xB81F8020},
|
||||
{arm.X1, arm.X0, -8, 4, 0xB81F8001},
|
||||
{arm.X0, arm.X1, -8, 8, 0xF81F8020},
|
||||
{arm.X1, arm.X0, -8, 8, 0xF81F8001},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("stur %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset, pattern.Length)
|
||||
code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Offset, pattern.Length)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
38
src/arm/Sub.go
Normal file
38
src/arm/Sub.go
Normal file
@ -0,0 +1,38 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// SubRegisterNumber subtracts a number from the given register.
|
||||
func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) {
|
||||
return subRegisterNumber(destination, source, number, 0)
|
||||
}
|
||||
|
||||
// SubRegisterRegister subtracts a register from a register.
|
||||
func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
|
||||
return subRegisterRegister(destination, source, operand, 0)
|
||||
}
|
||||
|
||||
// subRegisterNumber subtracts the register and optionally updates the condition flags based on the result.
|
||||
func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) {
|
||||
shift := uint32(0)
|
||||
|
||||
if number > mask12 {
|
||||
if number&mask12 != 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
shift = 1
|
||||
number >>= 12
|
||||
|
||||
if number > mask12 {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number), true
|
||||
}
|
||||
|
||||
// subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result.
|
||||
func subRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 {
|
||||
return flags<<29 | 0b11001011000<<21 | reg3(destination, source, operand)
|
||||
}
|
46
src/arm/Sub_test.go
Normal file
46
src/arm/Sub_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestSubRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Number int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X0, 1, 0xD1000400},
|
||||
{arm.X0, arm.X0, 0x1000, 0xD1400400},
|
||||
{arm.SP, arm.SP, 16, 0xD10043FF},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number)
|
||||
code, encodable := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
|
||||
assert.True(t, encodable)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, 0xCB020020},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("sub %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
|
||||
code := arm.SubRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
6
src/arm/Syscall.go
Normal file
6
src/arm/Syscall.go
Normal file
@ -0,0 +1,6 @@
|
||||
package arm
|
||||
|
||||
// Syscall is the primary way to communicate with the OS kernel.
|
||||
func Syscall() uint32 {
|
||||
return 0xD4000001
|
||||
}
|
14
src/arm/Xor.go
Normal file
14
src/arm/Xor.go
Normal file
@ -0,0 +1,14 @@
|
||||
package arm
|
||||
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// XorRegisterNumber performs a bitwise XOR using a register and a number.
|
||||
func XorRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) {
|
||||
n, immr, imms, encodable := encodeLogicalImmediate(uint(number))
|
||||
return 0b110100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable
|
||||
}
|
||||
|
||||
// XorRegisterRegister performs a bitwise XOR using two registers.
|
||||
func XorRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
|
||||
return 0b11001010<<24 | reg3(destination, source, operand)
|
||||
}
|
49
src/arm/Xor_test.go
Normal file
49
src/arm/Xor_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestXorRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Number int
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, 1, 0xD2400020},
|
||||
{arm.X0, arm.X1, 2, 0xD27F0020},
|
||||
{arm.X0, arm.X1, 3, 0xD2400420},
|
||||
{arm.X0, arm.X1, 7, 0xD2400820},
|
||||
{arm.X0, arm.X1, 16, 0xD27C0020},
|
||||
{arm.X0, arm.X1, 255, 0xD2401C20},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("eor %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number)
|
||||
code, encodable := arm.XorRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
|
||||
assert.True(t, encodable)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Operand cpu.Register
|
||||
Code uint32
|
||||
}{
|
||||
{arm.X0, arm.X1, arm.X2, 0xCA020020},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("eor %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
|
||||
code := arm.XorRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand)
|
||||
assert.Equal(t, code, pattern.Code)
|
||||
}
|
||||
}
|
37
src/arm/arm_test.go
Normal file
37
src/arm/arm_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestConstants(t *testing.T) {
|
||||
assert.DeepEqual(t, arm.Nop(), 0xD503201F)
|
||||
assert.DeepEqual(t, arm.Return(), 0xD65F03C0)
|
||||
assert.DeepEqual(t, arm.Syscall(), 0xD4000001)
|
||||
}
|
||||
|
||||
func TestNotEncodable(t *testing.T) {
|
||||
_, encodable := arm.AndRegisterNumber(arm.X0, arm.X0, 0)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, 0)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, 0)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.AndRegisterNumber(arm.X0, arm.X0, -1)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, -1)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, -1)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xFFFF)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xF0000000)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xFFFF)
|
||||
assert.False(t, encodable)
|
||||
_, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xF0000000)
|
||||
assert.False(t, encodable)
|
||||
}
|
33
src/arm/bitmask.go
Normal file
33
src/arm/bitmask.go
Normal file
@ -0,0 +1,33 @@
|
||||
package arm
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// encodeLogicalImmediate encodes a bitmask immediate.
|
||||
// The algorithm used here was made by Dougall Johnson.
|
||||
func encodeLogicalImmediate(val uint) (N int, immr int, imms int, encodable bool) {
|
||||
if val == 0 || ^val == 0 {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
|
||||
rotation := bits.TrailingZeros(clearTrailingOnes(val))
|
||||
normalized := bits.RotateLeft(val, -(rotation & 63))
|
||||
|
||||
zeroes := bits.LeadingZeros(normalized)
|
||||
ones := bits.TrailingZeros(^normalized)
|
||||
size := zeroes + ones
|
||||
|
||||
immr = -rotation & (size - 1)
|
||||
imms = -(size << 1) | (ones - 1)
|
||||
N = (size >> 6)
|
||||
|
||||
if bits.RotateLeft(val, -(size&63)) != val {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
|
||||
return N, immr, (imms & 0x3F), true
|
||||
}
|
||||
|
||||
// clearTrailingOnes clears trailing one bits.
|
||||
func clearTrailingOnes(x uint) uint {
|
||||
return x & (x + 1)
|
||||
}
|
22
src/arm/condition.go
Normal file
22
src/arm/condition.go
Normal file
@ -0,0 +1,22 @@
|
||||
package arm
|
||||
|
||||
type condition uint8
|
||||
|
||||
const (
|
||||
EQ condition = iota
|
||||
NE
|
||||
CS
|
||||
CC
|
||||
MI
|
||||
PL
|
||||
VS
|
||||
VC
|
||||
HI
|
||||
LS
|
||||
GE
|
||||
LT
|
||||
GT
|
||||
LE
|
||||
AL
|
||||
NV
|
||||
)
|
51
src/arm/encode.go
Normal file
51
src/arm/encode.go
Normal file
@ -0,0 +1,51 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
)
|
||||
|
||||
// memory encodes an instruction with a register, a base register and an offset.
|
||||
func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 {
|
||||
return uint32(imm9&mask9)<<12 | uint32(base)<<5 | uint32(destination)
|
||||
}
|
||||
|
||||
// pair encodes an instruction using a register pair with memory.
|
||||
func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uint32 {
|
||||
return uint32(imm7&mask7)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1)
|
||||
}
|
||||
|
||||
// regImm encodes an instruction with a register and an immediate.
|
||||
func regImm(d cpu.Register, imm16 uint16) uint32 {
|
||||
return uint32(imm16)<<5 | uint32(d)
|
||||
}
|
||||
|
||||
// regImmHw encodes an instruction with a register, an immediate and
|
||||
// the 2-bit halfword specifying which 16-bit region of the register is addressed.
|
||||
func regImmHw(d cpu.Register, hw int, imm16 uint16) uint32 {
|
||||
return uint32(hw)<<21 | uint32(imm16)<<5 | uint32(d)
|
||||
}
|
||||
|
||||
// reg2Imm encodes an instruction with 2 registers and an immediate.
|
||||
func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 {
|
||||
return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d)
|
||||
}
|
||||
|
||||
// reg2BitmaskImm encodes an instruction with 2 registers and a bitmask immediate.
|
||||
func reg2BitmaskImm(d cpu.Register, n cpu.Register, N int, immr int, imms int) uint32 {
|
||||
return uint32(N)<<22 | uint32(immr)<<16 | uint32(imms)<<10 | uint32(n)<<5 | uint32(d)
|
||||
}
|
||||
|
||||
// reg3 encodes an instruction with 3 registers.
|
||||
func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 {
|
||||
return uint32(m)<<16 | uint32(n)<<5 | uint32(d)
|
||||
}
|
||||
|
||||
// reg3Imm encodes an instruction with 3 registers.
|
||||
func reg3Imm(d cpu.Register, n cpu.Register, m cpu.Register, imm6 int) uint32 {
|
||||
return uint32(m)<<16 | uint32(imm6&mask6)<<10 | uint32(n)<<5 | uint32(d)
|
||||
}
|
||||
|
||||
// reg4 encodes an instruction with 4 registers.
|
||||
func reg4(d cpu.Register, n cpu.Register, m cpu.Register, a cpu.Register) uint32 {
|
||||
return uint32(m)<<16 | uint32(a)<<10 | uint32(n)<<5 | uint32(d)
|
||||
}
|
11
src/arm/mask.go
Normal file
11
src/arm/mask.go
Normal file
@ -0,0 +1,11 @@
|
||||
package arm
|
||||
|
||||
const (
|
||||
mask6 = 0b111111
|
||||
mask7 = 0b1111111
|
||||
mask9 = 0b1_11111111
|
||||
mask12 = 0b1111_11111111
|
||||
mask16 = 0b11111111_11111111
|
||||
mask19 = 0b111_11111111_11111111
|
||||
mask26 = 0b11_11111111_11111111_11111111
|
||||
)
|
@ -1,28 +1,10 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/data"
|
||||
)
|
||||
import "git.urbach.dev/cli/q/src/data"
|
||||
|
||||
// Assembler contains a list of instructions.
|
||||
type Assembler struct {
|
||||
Data data.Data
|
||||
Instructions []Instruction
|
||||
}
|
||||
|
||||
// Merge combines the contents of this assembler with another one.
|
||||
func (a *Assembler) Merge(b Assembler) {
|
||||
maps.Copy(a.Data, b.Data)
|
||||
a.Instructions = append(a.Instructions, b.Instructions...)
|
||||
}
|
||||
|
||||
// SetData sets the data for the given label.
|
||||
func (a *Assembler) SetData(label string, bytes []byte) {
|
||||
if a.Data == nil {
|
||||
a.Data = data.Data{}
|
||||
}
|
||||
|
||||
a.Data.Insert(label, bytes)
|
||||
Param Param
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package asm
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/cpu"
|
||||
import "git.urbach.dev/cli/q/src/cpu"
|
||||
|
||||
// CanSkip returns true if the register/register operation can be skipped.
|
||||
func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool {
|
||||
@ -15,19 +15,19 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi
|
||||
last := a.Instructions[len(a.Instructions)-1]
|
||||
|
||||
if mnemonic == MOVE && last.Mnemonic == MOVE {
|
||||
lastData, isRegReg := last.Data.(*RegisterRegister)
|
||||
|
||||
if !isRegReg {
|
||||
if last.Type != TypeRegisterRegister {
|
||||
return false
|
||||
}
|
||||
|
||||
lastData := a.Param.RegisterRegister[last.Index]
|
||||
|
||||
if lastData.Destination == right && lastData.Source == left {
|
||||
return true
|
||||
}
|
||||
|
||||
if lastData.Destination == left && lastData.Source == right {
|
||||
return true
|
||||
}
|
||||
|
||||
if lastData.Destination == right && lastData.Source == left {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -6,11 +6,19 @@ func (a *Assembler) CanSkipReturn() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic
|
||||
last := a.Instructions[len(a.Instructions)-1]
|
||||
|
||||
if lastMnemonic == RETURN || lastMnemonic == JUMP {
|
||||
if last.Mnemonic == RETURN || last.Mnemonic == JUMP {
|
||||
return true
|
||||
}
|
||||
|
||||
if last.Mnemonic == CALL && last.Type == TypeLabel {
|
||||
label := a.Param.Label[last.Index]
|
||||
|
||||
if label.String() == "core.exit" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
28
src/asm/DataString.go
Normal file
28
src/asm/DataString.go
Normal file
@ -0,0 +1,28 @@
|
||||
package asm
|
||||
|
||||
func (x Instruction) DataString(a *Assembler) string {
|
||||
switch x.Type {
|
||||
case TypeLabel:
|
||||
return a.Param.Label[x.Index].String()
|
||||
case TypeNumber:
|
||||
return a.Param.Number[x.Index].String()
|
||||
case TypeRegister:
|
||||
return a.Param.Register[x.Index].String()
|
||||
case TypeRegisterLabel:
|
||||
return a.Param.RegisterLabel[x.Index].String()
|
||||
case TypeRegisterNumber:
|
||||
return a.Param.RegisterNumber[x.Index].String()
|
||||
case TypeRegisterRegister:
|
||||
return a.Param.RegisterRegister[x.Index].String()
|
||||
case TypeMemory:
|
||||
return a.Param.Memory[x.Index].String()
|
||||
case TypeMemoryLabel:
|
||||
return a.Param.MemoryLabel[x.Index].String()
|
||||
case TypeMemoryNumber:
|
||||
return a.Param.MemoryNumber[x.Index].String()
|
||||
case TypeMemoryRegister:
|
||||
return a.Param.MemoryRegister[x.Index].String()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
3
src/asm/Index.go
Normal file
3
src/asm/Index.go
Normal file
@ -0,0 +1,3 @@
|
||||
package asm
|
||||
|
||||
type Index = uint16
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user