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
|
# q
|
||||||
|
|
||||||
A programming language that compiles down to machine code.
|
A programming language that quickly compiles to machine code.
|
||||||
|
|
||||||
## Features
|
## Goals
|
||||||
|
|
||||||
- Fast compilation
|
- Fast compilation
|
||||||
- High performance
|
- High performance
|
||||||
@ -12,7 +12,7 @@ A programming language that compiles down to machine code.
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://git.akyoto.dev/cli/q
|
git clone https://git.urbach.dev/cli/q
|
||||||
cd q
|
cd q
|
||||||
go build
|
go build
|
||||||
```
|
```
|
||||||
@ -33,7 +33,7 @@ You can take a look at the [examples](../examples).
|
|||||||
go run gotest.tools/gotestsum@latest
|
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
|
## Platforms
|
||||||
|
|
||||||
@ -45,16 +45,23 @@ q build examples/hello --os mac
|
|||||||
q build examples/hello --os windows
|
q build examples/hello --os windows
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or with different architectures:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
q build examples/hello --arch x86
|
||||||
|
q build examples/hello --arch arm
|
||||||
|
```
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
`q` is under heavy development and not ready for production yet.
|
`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
|
## License
|
||||||
|
|
||||||
Please see the [license documentation](https://akyoto.dev/license).
|
Please see the [license documentation](https://urbach.dev/license).
|
||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
- [x] Exclude unused functions
|
- [x] Exclude unused functions
|
||||||
- [x] Constant folding
|
- [x] Constant folding
|
||||||
|
- [ ] SSA form
|
||||||
- [ ] Constant propagation
|
- [ ] Constant propagation
|
||||||
- [ ] Function call inlining
|
- [ ] Function call inlining
|
||||||
- [ ] Loop unrolls
|
- [ ] Loop unrolls
|
||||||
|
@ -8,6 +8,6 @@ main() {
|
|||||||
buffer[2] = 'l'
|
buffer[2] = 'l'
|
||||||
buffer[3] = 'l'
|
buffer[3] = 'l'
|
||||||
buffer[4] = 'o'
|
buffer[4] = 'o'
|
||||||
io.write(1, buffer)
|
io.write(buffer)
|
||||||
mem.free(buffer)
|
mem.free(buffer)
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import fmt
|
||||||
import io
|
import io
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
@ -12,12 +13,12 @@ collatz(x int) {
|
|||||||
x = 3 * x + 1
|
x = 3 * x + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
io.number(x)
|
fmt.decimal(x)
|
||||||
|
|
||||||
if x == 1 {
|
if x == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
io.out(" ")
|
io.write(" ")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import io
|
import fmt
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
io.number(factorial(5))
|
fmt.decimal(factorial(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
factorial(x int) -> int {
|
factorial(x int) -> int {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import io
|
import fmt
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
io.number(fibonacci(10))
|
fmt.decimal(fibonacci(10))
|
||||||
}
|
}
|
||||||
|
|
||||||
fibonacci(x int) -> int {
|
fibonacci(x int) -> int {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import fmt
|
||||||
import io
|
import io
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
@ -9,10 +10,10 @@ fizzbuzz(n int) {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
switch {
|
switch {
|
||||||
x % 15 == 0 { io.out("FizzBuzz") }
|
x % 15 == 0 { io.write("FizzBuzz") }
|
||||||
x % 5 == 0 { io.out("Buzz") }
|
x % 5 == 0 { io.write("Buzz") }
|
||||||
x % 3 == 0 { io.out("Fizz") }
|
x % 3 == 0 { io.write("Fizz") }
|
||||||
_ { io.number(x) }
|
_ { fmt.decimal(x) }
|
||||||
}
|
}
|
||||||
|
|
||||||
x += 1
|
x += 1
|
||||||
@ -21,6 +22,6 @@ fizzbuzz(n int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
io.out(" ")
|
io.write(" ")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import io
|
import fmt
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
io.number(gcd(1071, 462))
|
fmt.decimal(gcd(1071, 462))
|
||||||
}
|
}
|
||||||
|
|
||||||
gcd(a int, b int) -> int {
|
gcd(a int, b int) -> int {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import io
|
import io
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
io.out("Hello\n")
|
io.write("Hello\n")
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import io
|
import fmt
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
io.number(9223372036854775807)
|
fmt.decimal(9223372036854775807)
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
|
import io
|
||||||
import mem
|
import mem
|
||||||
import sys
|
|
||||||
|
|
||||||
struct Point {
|
Point {
|
||||||
x int
|
x int
|
||||||
y int
|
y int
|
||||||
}
|
}
|
||||||
@ -29,6 +29,6 @@ print(p *Point) {
|
|||||||
out[5] = ' '
|
out[5] = ' '
|
||||||
out[6] = '0' + p.y
|
out[6] = '0' + p.y
|
||||||
out[7] = '\n'
|
out[7] = '\n'
|
||||||
sys.write(1, out, 8)
|
io.write(out)
|
||||||
mem.free(out)
|
mem.free(out)
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import fmt
|
||||||
import io
|
import io
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
@ -9,12 +10,12 @@ main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPrime(i) == 1 {
|
if isPrime(i) {
|
||||||
if i != 2 {
|
if i != 2 {
|
||||||
io.out(" ")
|
io.write(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
io.number(i)
|
fmt.decimal(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
@ -23,22 +24,22 @@ main() {
|
|||||||
|
|
||||||
isPrime(x int) -> bool {
|
isPrime(x int) -> bool {
|
||||||
if x == 2 {
|
if x == 2 {
|
||||||
return 1
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if x % 2 == 0 {
|
if x % 2 == 0 {
|
||||||
return 0
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
i := 3
|
i := 3
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if i * i > x {
|
if i * i > x {
|
||||||
return 1
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if x % i == 0 {
|
if x % i == 0 {
|
||||||
return 0
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 2
|
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 io
|
||||||
import net
|
import net
|
||||||
import sys
|
import sys
|
||||||
@ -9,30 +6,30 @@ main() {
|
|||||||
socket := sys.socket(2, 1, 0)
|
socket := sys.socket(2, 1, 0)
|
||||||
|
|
||||||
if socket < 0 {
|
if socket < 0 {
|
||||||
io.error("socket error\n")
|
io.write("socket error\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if net.bind(socket, 8080) != 0 {
|
if net.bind(socket, 8080) != 0 {
|
||||||
io.error("bind error\n")
|
io.write("bind error\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sys.listen(socket, 128) != 0 {
|
if sys.listen(socket, 128) != 0 {
|
||||||
io.error("listen error\n")
|
io.write("listen error\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
io.out("listening...\n")
|
io.write("listening...\n")
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
conn := sys.accept(socket, 0, 0)
|
conn := sys.accept(socket, 0, 0)
|
||||||
|
|
||||||
if conn >= 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)
|
sys.close(conn)
|
||||||
} else {
|
} else {
|
||||||
io.error("accept error\n")
|
io.write("accept error\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,13 @@
|
|||||||
const idtype {
|
const {
|
||||||
pid 1
|
idtype {
|
||||||
}
|
pid 1
|
||||||
|
}
|
||||||
|
|
||||||
const state {
|
sig {
|
||||||
exited 0x4
|
chld 17
|
||||||
|
}
|
||||||
|
|
||||||
|
state {
|
||||||
|
exited 0x4
|
||||||
|
}
|
||||||
}
|
}
|
@ -7,15 +7,15 @@ main() {
|
|||||||
command := mem.alloc(length)
|
command := mem.alloc(length)
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
io.out("λ ")
|
io.write("λ ")
|
||||||
n := io.in(command)
|
n := io.read(command)
|
||||||
|
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
command[n-1] = 0
|
command[n-1] = 0
|
||||||
pid := sys.fork()
|
pid := sys.clone(sig.chld, 0, 0, 0, 0)
|
||||||
|
|
||||||
if pid == 0 {
|
if pid == 0 {
|
||||||
sys.execve(command, 0, 0)
|
sys.execve(command, 0, 0)
|
||||||
|
@ -10,6 +10,6 @@ main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
work() {
|
work() {
|
||||||
io.out("[ ] start\n")
|
io.write("[ ] start\n")
|
||||||
io.out("[x] end\n")
|
io.write("[x] end\n")
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
extern user32 {
|
|
||||||
MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
title := "Title."
|
title := "Title."
|
||||||
text := "Hi!"
|
text := "Hi!"
|
||||||
user32.MessageBoxA(0, text, title, 0x240040)
|
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
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.akyoto.dev/go/assert v0.1.3
|
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf
|
||||||
git.akyoto.dev/go/color v0.1.3
|
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.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE=
|
||||||
git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM=
|
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo=
|
||||||
git.akyoto.dev/go/color v0.1.3 h1:kqOVYaPJJDHi8qEwTZkZecQaUENBToaLmiiV1Gg++GM=
|
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A=
|
||||||
git.akyoto.dev/go/color v0.1.3/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss=
|
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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)
|
||||||
|
}
|
@ -5,4 +5,8 @@ init() {
|
|||||||
|
|
||||||
exit() {
|
exit() {
|
||||||
syscall(60, 0)
|
syscall(60, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
crash() {
|
||||||
|
syscall(60, 1)
|
||||||
}
|
}
|
@ -5,4 +5,8 @@ init() {
|
|||||||
|
|
||||||
exit() {
|
exit() {
|
||||||
syscall(0x2000001, 0)
|
syscall(0x2000001, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
crash() {
|
||||||
|
syscall(0x2000001, 1)
|
||||||
}
|
}
|
@ -9,12 +9,20 @@ exit() {
|
|||||||
kernel32.ExitProcess(0)
|
kernel32.ExitProcess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cp {
|
crash() {
|
||||||
utf8 65001
|
kernel32.ExitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
extern kernel32 {
|
const {
|
||||||
SetConsoleCP(cp uint)
|
cp {
|
||||||
SetConsoleOutputCP(cp uint)
|
utf8 65001
|
||||||
ExitProcess(code uint)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern {
|
||||||
|
kernel32 {
|
||||||
|
SetConsoleCP(cp uint)
|
||||||
|
SetConsoleOutputCP(cp uint)
|
||||||
|
ExitProcess(code uint)
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import mem
|
import mem
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
number(x int) {
|
decimal(x int) {
|
||||||
buffer := mem.alloc(20)
|
buffer := mem.alloc(20)
|
||||||
address, count := itoa(x, buffer)
|
address, count := itoa(x, buffer)
|
||||||
sys.write(1, address, count)
|
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
|
||||||
|
}
|
14
lib/io/io.q
14
lib/io/io.q
@ -1,9 +1,7 @@
|
|||||||
import sys
|
const {
|
||||||
|
std {
|
||||||
read(fd int, buffer []byte) -> int {
|
in 0
|
||||||
return sys.read(fd, buffer, len(buffer))
|
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
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
store(x, 8, length)
|
[x] = length
|
||||||
return x + 8
|
return x + 8
|
||||||
}
|
}
|
@ -1,7 +1,3 @@
|
|||||||
extern kernel32 {
|
|
||||||
VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any
|
|
||||||
}
|
|
||||||
|
|
||||||
alloc(length int) -> []byte {
|
alloc(length int) -> []byte {
|
||||||
x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite)
|
x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite)
|
||||||
|
|
||||||
@ -9,6 +5,12 @@ alloc(length int) -> []byte {
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
store(x, 8, length)
|
[x] = length
|
||||||
return x + 8
|
return x + 8
|
||||||
|
}
|
||||||
|
|
||||||
|
extern {
|
||||||
|
kernel32 {
|
||||||
|
VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
const prot {
|
const {
|
||||||
read 0x1
|
prot {
|
||||||
write 0x2
|
read 0x1
|
||||||
}
|
write 0x2
|
||||||
|
}
|
||||||
|
|
||||||
const map {
|
map {
|
||||||
private 0x02
|
private 0x02
|
||||||
anonymous 0x20
|
anonymous 0x20
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
const prot {
|
const {
|
||||||
read 0x1
|
prot {
|
||||||
write 0x2
|
read 0x1
|
||||||
}
|
write 0x2
|
||||||
|
}
|
||||||
|
|
||||||
const map {
|
map {
|
||||||
private 0x02
|
private 0x02
|
||||||
anonymous 0x1000
|
anonymous 0x1000
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
const page {
|
const {
|
||||||
readwrite 0x0004
|
page {
|
||||||
}
|
readwrite 0x0004
|
||||||
|
}
|
||||||
|
|
||||||
const mem {
|
mem {
|
||||||
commit 0x1000
|
commit 0x1000
|
||||||
reserve 0x2000
|
reserve 0x2000
|
||||||
decommit 0x4000
|
decommit 0x4000
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
free(address []any) -> int {
|
free(address []any) {
|
||||||
return sys.munmap(address-8, len(address)+8)
|
sys.munmap(address-8, len(address)+8)
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
extern kernel32 {
|
free(address []any) {
|
||||||
VirtualFree(address *any, size uint, type uint32) -> bool
|
kernel32.VirtualFree(address-8, len(address)+8, mem.decommit)
|
||||||
}
|
}
|
||||||
|
|
||||||
free(address []any) -> int {
|
extern {
|
||||||
return kernel32.VirtualFree(address-8, len(address)+8, mem.decommit)
|
kernel32 {
|
||||||
|
VirtualFree(address *any, size uint, type uint32) -> bool
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
bind(socket int, port int) -> int {
|
bind(socket int, port uint16) -> int {
|
||||||
addr := new(sys.sockaddr_in)
|
addr := new(sys.sockaddr_in)
|
||||||
addr.sin_family = 2
|
addr.sin_family = 2
|
||||||
addr.sin_port = htons(port)
|
addr.sin_port = htons(port)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
bind(socket int, port int) -> int {
|
bind(socket int, port uint16) -> int {
|
||||||
addr := new(sys.sockaddr_in_bsd)
|
addr := new(sys.sockaddr_in_bsd)
|
||||||
addr.sin_family = 2
|
addr.sin_family = 2
|
||||||
addr.sin_port = htons(port)
|
addr.sin_port = htons(port)
|
||||||
|
@ -1,11 +1,34 @@
|
|||||||
struct sockaddr_in {
|
sockaddr_in {
|
||||||
sin_family int16
|
sin_family int16
|
||||||
sin_port int16
|
sin_port uint16
|
||||||
sin_addr int64
|
sin_addr int64
|
||||||
sin_zero int64
|
sin_zero int64
|
||||||
}
|
}
|
||||||
|
|
||||||
struct timespec {
|
file_stat {
|
||||||
seconds int64
|
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
|
nanoseconds int64
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
struct sockaddr_in_bsd {
|
sockaddr_in_bsd {
|
||||||
sin_len int8
|
sin_len int8
|
||||||
sin_family int8
|
sin_family int8
|
||||||
sin_port int16
|
sin_port uint16
|
||||||
sin_addr int64
|
sin_addr int64
|
||||||
sin_zero int64
|
sin_zero int64
|
||||||
}
|
}
|
@ -1,91 +1,91 @@
|
|||||||
read(fd int, buffer *byte, length int) -> int {
|
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 {
|
write(fd int, buffer *byte, length int) -> int {
|
||||||
return syscall(1, fd, buffer, length)
|
return syscall(n.write, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mmap(address int, length uint, protection int, flags int) -> *any {
|
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 {
|
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 {
|
clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int {
|
||||||
return syscall(56, flags, stack, parent, child, tls)
|
return syscall(n.clone, flags, stack, parent, child, tls)
|
||||||
}
|
|
||||||
|
|
||||||
fork() -> int {
|
|
||||||
return syscall(57)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
execve(path *any, argv *any, envp *any) -> int {
|
execve(path *any, argv *any, envp *any) -> int {
|
||||||
return syscall(59, path, argv, envp)
|
return syscall(n.execve, path, argv, envp)
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(status int) {
|
exit(status int) {
|
||||||
syscall(60, status)
|
syscall(n.exit, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
waitid(type int, id int, info *any, options int) -> int {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
getcwd(buffer *any, length int) -> int {
|
||||||
return syscall(79, buffer, length)
|
return syscall(n.getcwd, buffer, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
chdir(path *any) -> int {
|
chdir(path *any) -> int {
|
||||||
return syscall(80, path)
|
return syscall(n.chdir, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
rename(old *any, new *any) -> int {
|
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 {
|
mkdir(path *any, mode int) -> int {
|
||||||
return syscall(83, path, mode)
|
return syscall(n.mkdirat, -100, path, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
rmdir(path *any) -> int {
|
rmdir(path *any) -> int {
|
||||||
return syscall(84, path)
|
return syscall(n.unlinkat, -100, path, 0x200)
|
||||||
}
|
}
|
||||||
|
|
||||||
unlink(file *any) -> int {
|
unlink(file *any) -> int {
|
||||||
return syscall(87, file)
|
return syscall(n.unlinkat, -100, file, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
nanosleep(duration *timespec) -> int {
|
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)
|
fd = kernel32.GetStdHandle(-10 - fd)
|
||||||
kernel32.ReadConsole(fd, buffer, length, 0)
|
kernel32.ReadConsole(fd, buffer, uint32(length), 0)
|
||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
write(fd int64, buffer *byte, length int64) -> int64 {
|
write(fd int, buffer *byte, length int) -> int {
|
||||||
fd = kernel32.GetStdHandle(-10 - fd)
|
fd = kernel32.GetStdHandle(-10 - fd)
|
||||||
kernel32.WriteConsoleA(fd, buffer, length, 0)
|
kernel32.WriteConsoleA(fd, buffer, uint32(length), 0)
|
||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
extern kernel32 {
|
extern {
|
||||||
GetStdHandle(handle int64) -> int64
|
kernel32 {
|
||||||
ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool
|
GetStdHandle(handle int64) -> int64
|
||||||
WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool
|
ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool
|
||||||
|
WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,23 +1,23 @@
|
|||||||
import core
|
import core
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
const clone {
|
create(func *any) -> int {
|
||||||
vm 0x100
|
stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100)
|
||||||
fs 0x200
|
stack += 4096 - 8
|
||||||
files 0x400
|
[stack] = core.exit
|
||||||
sighand 0x800
|
stack -= 8
|
||||||
parent 0x8000
|
[stack] = func
|
||||||
thread 0x10000
|
return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0)
|
||||||
io 0x80000000
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create(func *any) -> int {
|
const {
|
||||||
size := 4096
|
clone {
|
||||||
stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000)
|
vm 0x100
|
||||||
stack += size
|
fs 0x200
|
||||||
stack -= 8
|
files 0x400
|
||||||
store(stack, 8, core.exit)
|
sighand 0x800
|
||||||
stack -= 8
|
parent 0x8000
|
||||||
store(stack, 8, func)
|
thread 0x10000
|
||||||
return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0)
|
io 0x80000000
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
extern kernel32 {
|
|
||||||
CreateThread(attributes int, stackSize int, address *any, parameter int) -> int
|
|
||||||
}
|
|
||||||
|
|
||||||
create(func *any) -> int {
|
create(func *any) -> int {
|
||||||
return kernel32.CreateThread(0, 4096, func, 0)
|
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/cli"
|
"git.urbach.dev/cli/q/src/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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
|
package arm
|
||||||
|
|
||||||
import "git.akyoto.dev/cli/q/src/cpu"
|
import "git.urbach.dev/cli/q/src/cpu"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
X0 cpu.Register = iota
|
X0 cpu.Register = iota // Function arguments and return values [0-7]
|
||||||
X1
|
X1
|
||||||
X2
|
X2
|
||||||
X3
|
X3
|
||||||
@ -11,18 +11,18 @@ const (
|
|||||||
X5
|
X5
|
||||||
X6
|
X6
|
||||||
X7
|
X7
|
||||||
X8
|
X8 // Indirect result location register (used to pass a pointer to a structure return value)
|
||||||
X9
|
X9 // Temporary registers (caller-saved, used for general computation) [9-15]
|
||||||
X10
|
X10
|
||||||
X11
|
X11
|
||||||
X12
|
X12
|
||||||
X13
|
X13
|
||||||
X14
|
X14
|
||||||
X15
|
X15
|
||||||
X16
|
X16 // Intra-procedure call scratch registers [16-17]
|
||||||
X17
|
X17
|
||||||
X18
|
X18 // Platform register (reserved by the platform ABI for thread-local storage)
|
||||||
X19
|
X19 // Callee-saved registers (must be preserved across function calls) [19-28]
|
||||||
X20
|
X20
|
||||||
X21
|
X21
|
||||||
X22
|
X22
|
||||||
@ -37,8 +37,23 @@ const (
|
|||||||
SP // Stack pointer
|
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 (
|
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}
|
WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7}
|
||||||
WindowsOutputRegisters = []cpu.Register{X0, X1}
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/arm"
|
"git.urbach.dev/cli/q/src/arm"
|
||||||
"git.akyoto.dev/go/assert"
|
"git.urbach.dev/go/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisters(t *testing.T) {
|
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
|
package asm
|
||||||
|
|
||||||
import (
|
import "git.urbach.dev/cli/q/src/data"
|
||||||
"maps"
|
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/data"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Assembler contains a list of instructions.
|
// Assembler contains a list of instructions.
|
||||||
type Assembler struct {
|
type Assembler struct {
|
||||||
Data data.Data
|
Data data.Data
|
||||||
Instructions []Instruction
|
Instructions []Instruction
|
||||||
}
|
Param Param
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package asm
|
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.
|
// CanSkip returns true if the register/register operation can be skipped.
|
||||||
func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool {
|
func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool {
|
||||||
@ -15,17 +15,17 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi
|
|||||||
last := a.Instructions[len(a.Instructions)-1]
|
last := a.Instructions[len(a.Instructions)-1]
|
||||||
|
|
||||||
if mnemonic == MOVE && last.Mnemonic == MOVE {
|
if mnemonic == MOVE && last.Mnemonic == MOVE {
|
||||||
lastData, isRegReg := last.Data.(*RegisterRegister)
|
if last.Type != TypeRegisterRegister {
|
||||||
|
|
||||||
if !isRegReg {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastData.Destination == left && lastData.Source == right {
|
lastData := a.Param.RegisterRegister[last.Index]
|
||||||
|
|
||||||
|
if lastData.Destination == right && lastData.Source == left {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastData.Destination == right && lastData.Source == left {
|
if lastData.Destination == left && lastData.Source == right {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,19 @@ func (a *Assembler) CanSkipReturn() bool {
|
|||||||
return false
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if last.Mnemonic == CALL && last.Type == TypeLabel {
|
||||||
|
label := a.Param.Label[last.Index]
|
||||||
|
|
||||||
|
if label.String() == "core.exit" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
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