Compare commits

...

90 Commits

Author SHA1 Message Date
9d6356e8a9 Implemented code pointers on arm64 2025-04-29 18:12:51 +02:00
aae3a552bb Updated dependencies 2025-04-25 12:12:05 +02:00
9107a06df5 Improved Windows support in the tokenizer 2025-04-18 14:48:59 +02:00
a023b058f8 Simplified register names 2025-04-17 14:16:00 +02:00
df725a2b23 Refactored arm package 2025-04-16 17:38:48 +02:00
53ecf40229 Implemented stat syscall 2025-04-15 18:56:01 +02:00
392b35423f Improved test descriptions 2025-04-15 11:51:03 +02:00
80aa833a9b Added a function for memory size alignment 2025-04-15 11:08:33 +02:00
19a8154e06 Removed old syscalls 2025-04-15 09:31:49 +02:00
6483573923 Improved type safety for memory writes 2025-04-14 15:58:15 +02:00
43a006e4af Replaced builtin store function with a new syntax 2025-04-11 14:21:51 +02:00
a9d783c675 Refactored the interface of the io package 2025-04-07 18:02:36 +02:00
08ea91f46c Added fmt package 2025-04-07 15:13:57 +02:00
cac2bad2fe Refactored assembly compilation 2025-04-06 20:37:26 +02:00
543558a02b Modified const and extern to always use blocks 2025-04-05 19:34:50 +02:00
ba34637dc1 Moved const and extern identifiers to the left 2025-04-04 20:15:31 +02:00
863fb7de5a Removed struct keyword 2025-04-04 13:32:55 +02:00
9302eaef2f Implemented division by immediates in the IR 2025-03-31 15:51:04 +02:00
008f097186 Simplified CPU state 2025-03-31 14:36:42 +02:00
0bc52fb673 Improved documentation 2025-03-28 15:35:29 +01:00
33b91e7bf4 Added more tests 2025-03-24 23:17:51 +01:00
ae928156c5 Added register documentation for arm64 2025-03-23 12:57:05 +01:00
597eb08ff6 Implemented an ordered set for function dependencies 2025-03-22 12:38:41 +01:00
ac8bd65054 Implemented instruction splitting as a generator 2025-03-20 13:53:23 +01:00
c2e489f987 Implemented token splitting as a generator 2025-03-20 00:26:42 +01:00
9a8bf8ff64 Updated documentation 2025-03-18 18:48:45 +01:00
eb27595593 Implemented register negation on arm64 2025-03-17 14:35:12 +01:00
76356b2d38 Added fallback for numbers that can't be encoded on arm64 2025-03-17 13:36:42 +01:00
bcb04a4cec Fixed move with negative numbers on arm64 2025-03-15 20:34:47 +01:00
b8f05c8994 Implemented bit shifting on arm64 2025-03-15 19:45:14 +01:00
93042f81e7 Fixed add and sub instruction encoding on arm64 2025-03-15 15:51:08 +01:00
fea4e4cbe7 Fixed move instruction encoding on arm64 2025-03-15 13:49:30 +01:00
99ef02bbd6 Implemented more move instructions on arm64 2025-03-14 21:16:35 +01:00
5f339187e6 Implemented bitwise operations on arm64 2025-03-14 18:37:39 +01:00
e123a26a1d Implemented bitwise AND on arm64 2025-03-14 17:45:39 +01:00
72272ed9af Implemented multiplication with a number on arm64 2025-03-14 12:08:14 +01:00
1d0e49f0e3 Fixed move with negative numbers on arm64 2025-03-14 11:58:36 +01:00
9410287605 Fixed comparison with negative numbers on arm64 2025-03-14 11:14:42 +01:00
9726f1d832 Added crash function 2025-03-14 09:52:00 +01:00
06f3af256b Added more tests 2025-03-14 00:08:58 +01:00
ac14ab4f7a Implemented more arm64 instructions 2025-03-13 23:12:15 +01:00
5b3769a0db Fixed push and pop on arm64 2025-03-13 17:20:11 +01:00
c1913d99d0 Added label type 2025-03-13 16:57:13 +01:00
d96c351b4b Added more tests 2025-03-13 16:02:09 +01:00
5a5061c5d7 Implemented more arm64 instructions 2025-03-13 13:17:58 +01:00
dc664dbf5a Added wasm target 2025-03-12 23:15:43 +01:00
450e634d79 Implemented arm64 instructions: ldp and stp 2025-03-12 12:29:55 +01:00
03c8dfa34c Updated dependencies 2025-03-11 14:31:06 +01:00
e7a06f5b26 Improved assembler performance 2025-03-11 06:31:21 +01:00
d2ad8c8310 Updated dependencies 2025-03-10 21:10:19 +01:00
325dd126f9 Added allocator test 2025-03-10 16:58:17 +01:00
7807271f43 Added struct lifetime test 2025-03-10 15:58:31 +01:00
0d562e8002 Simplified x86 encoder 2025-03-09 23:27:40 +01:00
6c659a2b0c Added more tests 2025-03-09 19:24:15 +01:00
016938932f Fixed incorrect register lifetime in for loops 2025-03-09 17:51:24 +01:00
947a8db937 Implemented dynamic section alignment 2025-03-09 00:23:23 +01:00
b67f3b0977 Added exe package to manage sections 2025-03-08 22:14:24 +01:00
919d94e0f4 Added optional section headers 2025-03-07 16:55:30 +01:00
0bf299d007 Implemented position independent addresses for x86 2025-03-07 11:39:13 +01:00
0ac7fc9a85 Implemented more arm64 instructions 2025-03-06 23:13:14 +01:00
cb908e7b31 Updated readme 2025-03-06 17:05:33 +01:00
7fd29ea249 Updated donation links 2025-03-06 17:02:16 +01:00
7798eca074 Enabled arm64 encoding 2025-03-06 16:54:28 +01:00
5f522b519a Fixed incorrect architecture detection 2025-03-06 14:17:09 +01:00
2f09b96f34 Added basic support for arm64 2025-03-06 13:40:17 +01:00
14abb8202b Removed unnecessary list of registers 2025-03-05 14:41:58 +01:00
f2db223684 Improved performance of the data finalizer 2025-03-04 16:54:17 +01:00
e5f0123eea Removed dead code 2025-03-04 13:35:55 +01:00
df6f7d5a57 Implemented parameter passing for function pointers 2025-03-03 14:32:11 +01:00
751614e7c0 Implemented dependency tracking 2025-03-03 12:14:53 +01:00
08660ad845 Implemented calls using memory addresses 2025-03-03 00:53:41 +01:00
ea233d789d Simplified compilation of function calls 2025-03-02 21:36:23 +01:00
c3054369e3 Implemented register calls 2025-03-02 17:53:18 +01:00
d7f30d8319 Fixed missing values of casts 2025-03-01 23:04:37 +01:00
4428b09de2 Switched to pointer receivers for values 2025-03-01 18:38:00 +01:00
8ff6faa310 Fixed server example for Mac 2025-03-01 12:48:34 +01:00
e032733a92 Added more tests 2025-03-01 12:19:51 +01:00
6c0ab72f8f Improved implementation of assign operators 2025-03-01 11:34:36 +01:00
c1427eb7f6 Added more tests 2025-02-28 19:52:21 +01:00
be6eafddf5 Fixed inconsistent lifetimes 2025-02-28 19:37:06 +01:00
a5a8f0f503 Simplified expression evaluation 2025-02-28 16:07:10 +01:00
b67361c035 Implemented Value interface 2025-02-28 12:15:19 +01:00
31423ccc08 Simplified Evaluate function 2025-02-27 23:28:17 +01:00
e7afb2dab5 Added eval package 2025-02-27 19:45:18 +01:00
efb3089211 Simplified Evaluate function 2025-02-27 15:30:44 +01:00
9f78733d5d Added Value type 2025-02-27 14:16:25 +01:00
ae6530aadb Added placeholders for type casts 2025-02-27 00:18:34 +01:00
bbf2970c4e Simplified constant folding 2025-02-26 20:02:53 +01:00
ebe53b3b50 Updated documentation 2025-02-26 14:47:55 +01:00
5f46a6f14e Implemented boolean literals 2025-02-26 14:33:41 +01:00
353 changed files with 6560 additions and 3109 deletions

View File

@ -1,8 +1,8 @@
# q
A programming language that compiles down to machine code.
A programming language that quickly compiles to machine code.
## Features
## Goals
- Fast compilation
- High performance
@ -33,7 +33,7 @@ You can take a look at the [examples](../examples).
go run gotest.tools/gotestsum@latest
```
This will run over 350 [tests](../tests) in various categories.
This will run over 400 [tests](../tests) in various categories.
## Platforms
@ -45,12 +45,19 @@ q build examples/hello --os mac
q build examples/hello --os windows
```
Or with different architectures:
```shell
q build examples/hello --arch x86
q build examples/hello --arch arm
```
## Status
`q` is under heavy development and not ready for production yet.
Feel free to [get in touch](https://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

View File

@ -49,6 +49,7 @@
- [x] Exclude unused functions
- [x] Constant folding
- [ ] SSA form
- [ ] Constant propagation
- [ ] Function call inlining
- [ ] Loop unrolls

View File

@ -8,6 +8,6 @@ main() {
buffer[2] = 'l'
buffer[3] = 'l'
buffer[4] = 'o'
io.write(1, buffer)
io.write(buffer)
mem.free(buffer)
}

View File

@ -1,3 +1,4 @@
import fmt
import io
main() {
@ -12,12 +13,12 @@ collatz(x int) {
x = 3 * x + 1
}
io.number(x)
fmt.decimal(x)
if x == 1 {
return
}
io.out(" ")
io.write(" ")
}
}

View File

@ -1,7 +1,7 @@
import io
import fmt
main() {
io.number(factorial(5))
fmt.decimal(factorial(5))
}
factorial(x int) -> int {

View File

@ -1,7 +1,7 @@
import io
import fmt
main() {
io.number(fibonacci(10))
fmt.decimal(fibonacci(10))
}
fibonacci(x int) -> int {

View File

@ -1,3 +1,4 @@
import fmt
import io
main() {
@ -9,10 +10,10 @@ fizzbuzz(n int) {
loop {
switch {
x % 15 == 0 { io.out("FizzBuzz") }
x % 5 == 0 { io.out("Buzz") }
x % 3 == 0 { io.out("Fizz") }
_ { io.number(x) }
x % 15 == 0 { io.write("FizzBuzz") }
x % 5 == 0 { io.write("Buzz") }
x % 3 == 0 { io.write("Fizz") }
_ { fmt.decimal(x) }
}
x += 1
@ -21,6 +22,6 @@ fizzbuzz(n int) {
return
}
io.out(" ")
io.write(" ")
}
}

View File

@ -1,7 +1,7 @@
import io
import fmt
main() {
io.number(gcd(1071, 462))
fmt.decimal(gcd(1071, 462))
}
gcd(a int, b int) -> int {

View File

@ -1,5 +1,5 @@
import io
main() {
io.out("Hello\n")
io.write("Hello\n")
}

View File

@ -1,5 +1,5 @@
import io
import fmt
main() {
io.number(9223372036854775807)
fmt.decimal(9223372036854775807)
}

View File

@ -1,7 +1,7 @@
import io
import mem
import sys
struct Point {
Point {
x int
y int
}
@ -29,6 +29,6 @@ print(p *Point) {
out[5] = ' '
out[6] = '0' + p.y
out[7] = '\n'
sys.write(1, out, 8)
io.write(out)
mem.free(out)
}

View File

@ -1,3 +1,4 @@
import fmt
import io
main() {
@ -9,12 +10,12 @@ main() {
return
}
if isPrime(i) == 1 {
if isPrime(i) {
if i != 2 {
io.out(" ")
io.write(" ")
}
io.number(i)
fmt.decimal(i)
}
i += 1
@ -23,22 +24,22 @@ main() {
isPrime(x int) -> bool {
if x == 2 {
return 1
return true
}
if x % 2 == 0 {
return 0
return false
}
i := 3
loop {
if i * i > x {
return 1
return true
}
if x % i == 0 {
return 0
return false
}
i += 2

View 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)
}

View File

@ -1,6 +1,3 @@
// Open server and client in 2 terminals:
// [1] q run examples/server
// [2] curl http://127.0.0.1:8080
import io
import net
import sys
@ -9,30 +6,30 @@ main() {
socket := sys.socket(2, 1, 0)
if socket < 0 {
io.error("socket error\n")
io.write("socket error\n")
sys.exit(1)
}
if net.bind(socket, 8080) != 0 {
io.error("bind error\n")
io.write("bind error\n")
sys.exit(1)
}
if sys.listen(socket, 128) != 0 {
io.error("listen error\n")
io.write("listen error\n")
sys.exit(1)
}
io.out("listening...\n")
io.write("listening...\n")
loop {
conn := sys.accept(socket, 0, 0)
if conn >= 0 {
io.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n")
io.writeTo(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n")
sys.close(conn)
} else {
io.error("accept error\n")
io.write("accept error\n")
}
}
}

View File

@ -1,7 +1,13 @@
const idtype {
const {
idtype {
pid 1
}
const state {
sig {
chld 17
}
state {
exited 0x4
}
}

View File

@ -7,15 +7,15 @@ main() {
command := mem.alloc(length)
loop {
io.out("λ ")
n := io.in(command)
io.write("λ ")
n := io.read(command)
if n <= 0 {
return
}
command[n-1] = 0
pid := sys.fork()
pid := sys.clone(sig.chld, 0, 0, 0, 0)
if pid == 0 {
sys.execve(command, 0, 0)

View File

@ -10,6 +10,6 @@ main() {
}
work() {
io.out("[ ] start\n")
io.out("[x] end\n")
io.write("[ ] start\n")
io.write("[x] end\n")
}

View File

@ -1,9 +1,11 @@
extern user32 {
MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int
}
main() {
title := "Title."
text := "Hi!"
user32.MessageBoxA(0, text, title, 0x240040)
}
extern {
user32 {
MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int
}
}

4
go.mod
View File

@ -4,7 +4,7 @@ go 1.24
require (
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf
git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d
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

8
go.sum
View File

@ -1,6 +1,6 @@
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE=
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo=
git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d h1:j1ARCrjUYE/c1STH/s6UYGQoOXAkxXll4AFhTb8P2GE=
git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d/go.mod h1:UE8tQTrlWeVPKhfPZ9G5QrDFRhL9EoPazcDgfgd5Xsk=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A=
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

12
lib/core/core_linux_arm.q Normal file
View File

@ -0,0 +1,12 @@
init() {
main.main()
exit()
}
exit() {
syscall(93, 0)
}
crash() {
syscall(93, 1)
}

View File

@ -6,3 +6,7 @@ init() {
exit() {
syscall(60, 0)
}
crash() {
syscall(60, 1)
}

View File

@ -6,3 +6,7 @@ init() {
exit() {
syscall(0x2000001, 0)
}
crash() {
syscall(0x2000001, 1)
}

View File

@ -9,12 +9,20 @@ exit() {
kernel32.ExitProcess(0)
}
const cp {
utf8 65001
crash() {
kernel32.ExitProcess(1)
}
extern kernel32 {
const {
cp {
utf8 65001
}
}
extern {
kernel32 {
SetConsoleCP(cp uint)
SetConsoleOutputCP(cp uint)
ExitProcess(code uint)
}
}

View File

@ -1,7 +1,7 @@
import mem
import sys
number(x int) {
decimal(x int) {
buffer := mem.alloc(20)
address, count := itoa(x, buffer)
sys.write(1, address, count)

27
lib/fs/fs.q Normal file
View 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
}

View File

@ -1,9 +1,7 @@
import sys
read(fd int, buffer []byte) -> int {
return sys.read(fd, buffer, len(buffer))
const {
std {
in 0
out 1
err 2
}
write(fd int, buffer []byte) -> int {
return sys.write(fd, buffer, len(buffer))
}

9
lib/io/read.q Normal file
View 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))
}

View File

@ -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
View 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
View 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
}

View File

@ -7,6 +7,6 @@ alloc(length int) -> []byte {
return x
}
store(x, 8, length)
[x] = length
return x + 8
}

View File

@ -1,7 +1,3 @@
extern kernel32 {
VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any
}
alloc(length int) -> []byte {
x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite)
@ -9,6 +5,12 @@ alloc(length int) -> []byte {
return x
}
store(x, 8, length)
[x] = length
return x + 8
}
extern {
kernel32 {
VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any
}
}

View File

@ -1,9 +1,11 @@
const prot {
const {
prot {
read 0x1
write 0x2
}
const map {
map {
private 0x02
anonymous 0x20
}
}

View File

@ -1,9 +1,11 @@
const prot {
const {
prot {
read 0x1
write 0x2
}
const map {
map {
private 0x02
anonymous 0x1000
}
}

View File

@ -1,9 +1,11 @@
const page {
const {
page {
readwrite 0x0004
}
const mem {
mem {
commit 0x1000
reserve 0x2000
decommit 0x4000
}
}

View File

@ -1,5 +1,5 @@
import sys
free(address []any) -> int {
return sys.munmap(address-8, len(address)+8)
free(address []any) {
sys.munmap(address-8, len(address)+8)
}

View File

@ -1,7 +1,9 @@
extern kernel32 {
VirtualFree(address *any, size uint, type uint32) -> bool
free(address []any) {
kernel32.VirtualFree(address-8, len(address)+8, mem.decommit)
}
free(address []any) -> int {
return kernel32.VirtualFree(address-8, len(address)+8, mem.decommit)
extern {
kernel32 {
VirtualFree(address *any, size uint, type uint32) -> bool
}
}

View File

@ -1,6 +1,6 @@
import sys
bind(socket int, port int) -> int {
bind(socket int, port uint16) -> int {
addr := new(sys.sockaddr_in)
addr.sin_family = 2
addr.sin_port = htons(port)

View File

@ -1,6 +1,6 @@
import sys
bind(socket int, port int) -> int {
bind(socket int, port uint16) -> int {
addr := new(sys.sockaddr_in_bsd)
addr.sin_family = 2
addr.sin_port = htons(port)

View File

@ -1,11 +1,34 @@
struct sockaddr_in {
sockaddr_in {
sin_family int16
sin_port int16
sin_port uint16
sin_addr int64
sin_zero int64
}
struct timespec {
file_stat {
st_dev uint64
st_ino uint64
st_nlink uint64
st_mode uint32
st_uid uint32
st_gid uint32
_ uint32
st_rdev uint64
st_size int64
st_blksize int64
st_blocks int64
st_atime int64
st_atime_nsec int64
st_mtime int64
st_mtime_nsec int64
st_ctime int64
st_ctime_nsec int64
_ int64
_ int64
_ int64
}
timespec {
seconds int64
nanoseconds int64
}

View File

@ -1,7 +1,7 @@
struct sockaddr_in_bsd {
sockaddr_in_bsd {
sin_len int8
sin_family int8
sin_port int16
sin_port uint16
sin_addr int64
sin_zero int64
}

View File

@ -1,91 +1,91 @@
read(fd int, buffer *byte, length int) -> int {
return syscall(0, fd, buffer, length)
return syscall(n.read, fd, buffer, length)
}
write(fd int, buffer *byte, length int) -> int {
return syscall(1, fd, buffer, length)
}
open(path *any, flags int, mode int) -> int {
return syscall(2, path, flags, mode)
}
close(fd int) -> int {
return syscall(3, fd)
return syscall(n.write, fd, buffer, length)
}
mmap(address int, length uint, protection int, flags int) -> *any {
return syscall(9, address, length, protection, flags)
return syscall(n.mmap, address, length, protection, flags)
}
munmap(address *any, length uint) -> int {
return syscall(11, address, length)
return syscall(n.munmap, address, length)
}
open(path *any, flags int, mode int) -> int {
return syscall(n.openat, -100, path, flags, mode)
}
close(fd int) -> int {
return syscall(n.close, fd)
}
stat(path *any, stat *file_stat) -> int {
return syscall(n.newfstatat, -100, path, stat, 0)
}
clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int {
return syscall(56, flags, stack, parent, child, tls)
}
fork() -> int {
return syscall(57)
return syscall(n.clone, flags, stack, parent, child, tls)
}
execve(path *any, argv *any, envp *any) -> int {
return syscall(59, path, argv, envp)
return syscall(n.execve, path, argv, envp)
}
exit(status int) {
syscall(60, status)
syscall(n.exit, status)
}
waitid(type int, id int, info *any, options int) -> int {
return syscall(247, type, id, info, options)
return syscall(n.waitid, type, id, info, options)
}
socket(family int, type int, protocol int) -> int {
return syscall(41, family, type, protocol)
return syscall(n.socket, family, type, protocol)
}
accept(fd int, address *any, length int) -> int {
return syscall(43, fd, address, length)
return syscall(n.accept, fd, address, length)
}
bind(fd int, address *sockaddr_in, length int) -> int {
return syscall(49, fd, address, length)
return syscall(n.bind, fd, address, length)
}
listen(fd int, backlog int) -> int {
return syscall(50, fd, backlog)
return syscall(n.listen, fd, backlog)
}
setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int {
return syscall(54, fd, level, optname, optval, optlen)
return syscall(n.setsockopt, fd, level, optname, optval, optlen)
}
getcwd(buffer *any, length int) -> int {
return syscall(79, buffer, length)
return syscall(n.getcwd, buffer, length)
}
chdir(path *any) -> int {
return syscall(80, path)
return syscall(n.chdir, path)
}
rename(old *any, new *any) -> int {
return syscall(82, old, new)
return syscall(n.renameat, -100, old, -100, new)
}
mkdir(path *any, mode int) -> int {
return syscall(83, path, mode)
return syscall(n.mkdirat, -100, path, mode)
}
rmdir(path *any) -> int {
return syscall(84, path)
return syscall(n.unlinkat, -100, path, 0x200)
}
unlink(file *any) -> int {
return syscall(87, file)
return syscall(n.unlinkat, -100, file, 0)
}
nanosleep(duration *timespec) -> int {
return syscall(35, duration, 0)
return syscall(n.nanosleep, duration, 0)
}

26
lib/sys/sys_linux_arm.q Normal file
View 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
View 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
}
}

View File

@ -1,17 +1,19 @@
read(fd int64, buffer *byte, length int64) -> int64 {
read(fd int, buffer *byte, length int) -> int {
fd = kernel32.GetStdHandle(-10 - fd)
kernel32.ReadConsole(fd, buffer, length, 0)
kernel32.ReadConsole(fd, buffer, uint32(length), 0)
return length
}
write(fd int64, buffer *byte, length int64) -> int64 {
write(fd int, buffer *byte, length int) -> int {
fd = kernel32.GetStdHandle(-10 - fd)
kernel32.WriteConsoleA(fd, buffer, length, 0)
kernel32.WriteConsoleA(fd, buffer, uint32(length), 0)
return length
}
extern kernel32 {
extern {
kernel32 {
GetStdHandle(handle int64) -> int64
ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool
WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool
}
}

View File

@ -1,7 +1,17 @@
import core
import sys
const clone {
create(func *any) -> int {
stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100)
stack += 4096 - 8
[stack] = core.exit
stack -= 8
[stack] = func
return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0)
}
const {
clone {
vm 0x100
fs 0x200
files 0x400
@ -10,14 +20,4 @@ const clone {
thread 0x10000
io 0x80000000
}
create(func *any) -> int {
size := 4096
stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000)
stack += size
stack -= 8
store(stack, 8, core.exit)
stack -= 8
store(stack, 8, func)
return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0)
}

View File

@ -1,7 +1,9 @@
extern kernel32 {
CreateThread(attributes int, stackSize int, address *any, parameter int) -> int
}
create(func *any) -> int {
return kernel32.CreateThread(0, 4096, func, 0)
}
extern {
kernel32 {
CreateThread(attributes int, stackSize int, address *any, parameter int) -> int
}
}

38
src/arm/Add.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}

View File

@ -3,7 +3,7 @@ package arm
import "git.urbach.dev/cli/q/src/cpu"
const (
X0 cpu.Register = iota
X0 cpu.Register = iota // Function arguments and return values [0-7]
X1
X2
X3
@ -11,18 +11,18 @@ const (
X5
X6
X7
X8
X9
X8 // Indirect result location register (used to pass a pointer to a structure return value)
X9 // Temporary registers (caller-saved, used for general computation) [9-15]
X10
X11
X12
X13
X14
X15
X16
X16 // Intra-procedure call scratch registers [16-17]
X17
X18
X19
X18 // Platform register (reserved by the platform ABI for thread-local storage)
X19 // Callee-saved registers (must be preserved across function calls) [19-28]
X20
X21
X22
@ -37,8 +37,23 @@ const (
SP // Stack pointer
)
const (
ZR = SP // Zero register uses the same numerical value as SP
TMP = X27 // Temporary register for the assembler
TMP2 = X28 // Temporary register for the assembler
)
var (
SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5}
InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5}
WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7}
WindowsOutputRegisters = []cpu.Register{X0, X1}
CPU = cpu.CPU{
General: []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26},
Input: InputRegisters,
Output: InputRegisters,
SyscallInput: []cpu.Register{X8, X0, X1, X2, X3, X4, X5},
SyscallOutput: []cpu.Register{X0, X1},
NumRegisters: 32,
}
)

View File

@ -8,5 +8,5 @@ import (
)
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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
)

View File

@ -1,28 +1,10 @@
package asm
import (
"maps"
"git.urbach.dev/cli/q/src/data"
)
import "git.urbach.dev/cli/q/src/data"
// Assembler contains a list of instructions.
type Assembler struct {
Data data.Data
Instructions []Instruction
}
// Merge combines the contents of this assembler with another one.
func (a *Assembler) Merge(b Assembler) {
maps.Copy(a.Data, b.Data)
a.Instructions = append(a.Instructions, b.Instructions...)
}
// SetData sets the data for the given label.
func (a *Assembler) SetData(label string, bytes []byte) {
if a.Data == nil {
a.Data = data.Data{}
}
a.Data.Insert(label, bytes)
Param Param
}

View File

@ -15,19 +15,19 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi
last := a.Instructions[len(a.Instructions)-1]
if mnemonic == MOVE && last.Mnemonic == MOVE {
lastData, isRegReg := last.Data.(*RegisterRegister)
if !isRegReg {
if last.Type != TypeRegisterRegister {
return false
}
lastData := a.Param.RegisterRegister[last.Index]
if lastData.Destination == right && lastData.Source == left {
return true
}
if lastData.Destination == left && lastData.Source == right {
return true
}
if lastData.Destination == right && lastData.Source == left {
return true
}
}
return false

View File

@ -6,11 +6,19 @@ func (a *Assembler) CanSkipReturn() bool {
return false
}
lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic
last := a.Instructions[len(a.Instructions)-1]
if lastMnemonic == RETURN || lastMnemonic == JUMP {
if last.Mnemonic == RETURN || last.Mnemonic == JUMP {
return true
}
if last.Mnemonic == CALL && last.Type == TypeLabel {
label := a.Param.Label[last.Index]
if label.String() == "core.exit" {
return true
}
}
return false
}

28
src/asm/DataString.go Normal file
View 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
View File

@ -0,0 +1,3 @@
package asm
type Index = uint16

View File

@ -1,9 +1,8 @@
package asm
import "fmt"
// Instruction represents a single instruction which can be converted to machine code.
type Instruction struct {
Data fmt.Stringer
Mnemonic Mnemonic
Type Type
Index Index
}

Some files were not shown because too many files have changed in this diff Show More