Compare commits

...

181 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
91f34bc88f Updated module path 2025-02-25 17:16:09 +01:00
98edfb19fd Changed test order 2025-02-25 17:06:46 +01:00
467fbd9725 Removed log from the standard library 2025-02-22 18:02:50 +01:00
df782ae1cb Added more tests 2025-02-22 13:58:07 +01:00
c088697446 Fixed incorrect division results 2025-02-22 12:54:23 +01:00
7598411c8f Updated documentation 2025-02-21 22:39:01 +01:00
264872e638 Simplified the functions that generate executables 2025-02-21 19:46:32 +01:00
91e01de3ad Implemented environment type 2025-02-21 17:35:42 +01:00
4caac57210 Improved label consistency 2025-02-21 17:04:13 +01:00
08436c31c0 Improved code consistency 2025-02-21 15:57:03 +01:00
1ca61190eb Fixed incorrect const values 2025-02-21 13:47:56 +01:00
04ece57b27 Added more tests 2025-02-21 11:45:32 +01:00
a42115c0fb Added byte type 2025-02-21 11:27:45 +01:00
8b932fb332 Improved documentation 2025-02-21 00:00:01 +01:00
36d0142573 Improved code quality 2025-02-20 16:55:17 +01:00
9779476fe7 Added more tests 2025-02-20 15:06:22 +01:00
b655950516 Simplified ast package 2025-02-20 14:12:25 +01:00
45a36a645a Implemented for loops 2025-02-19 23:46:17 +01:00
7922cff7ba Implemented loop iterators 2025-02-19 17:52:15 +01:00
1bc845e6f0 Implemented loads with register offsets 2025-02-19 15:36:42 +01:00
4cff697bf5 Updated documentation 2025-02-17 23:51:31 +01:00
3b76919fec Simplified config package 2025-02-17 23:36:11 +01:00
b7685fd7ec Simplified standard library 2025-02-17 17:49:42 +01:00
b8e37fafae Improved type system 2025-02-17 14:31:47 +01:00
3550f9e24e Fixed incorrect link 2025-02-17 11:17:36 +01:00
29b3bed452 Updated documentation 2025-02-17 11:15:59 +01:00
5905a5bfa5 Updated documentation 2025-02-17 11:05:17 +01:00
3fb6061c30 Fixed a special case in the expression parser 2025-02-17 10:42:44 +01:00
9574a26da7 Added more tests 2025-02-16 23:42:05 +01:00
216e66473e Improved shell example 2025-02-16 21:30:36 +01:00
596dbc6226 Updated todo list 2025-02-16 19:58:47 +01:00
dc3ba6504f Improved formatting of load and store commands 2025-02-16 19:29:22 +01:00
c633f94d29 Fixed fs package tests 2025-02-16 17:40:06 +01:00
7262f49717 Fixed build constraints for other systems 2025-02-16 17:39:03 +01:00
546da5d8b2 Fixed build constraints for FreeBSD 2025-02-16 17:32:48 +01:00
eeb8f86e46 Updated dependencies 2025-02-16 17:30:03 +01:00
9f125694be Added a program init function 2025-02-16 15:46:54 +01:00
00be603b8f Improved thread creation on Linux 2025-02-16 11:31:31 +01:00
d0bcd8cf9f Improved Windows ABI support 2025-02-15 18:23:33 +01:00
0a1a8f741d Implemented const keyword 2025-02-15 14:38:01 +01:00
be028a52d1 Fixed build package tests on Windows 2025-02-14 20:57:13 +01:00
4df847bf60 Fixed fs package tests on Windows 2025-02-14 20:35:44 +01:00
88b3f468d1 Updated Go version 2025-02-14 16:46:36 +01:00
d589b02570 Added more tests 2025-02-14 15:26:07 +01:00
c8b5c4dbfe Added more tests 2025-02-14 00:26:48 +01:00
e4cbb91f61 Improved error message for invalid instructions 2025-02-13 22:12:57 +01:00
aec22b0a7e Removed unnecessary parameter 2025-02-13 16:58:59 +01:00
0dffb79364 Simplified compiler package 2025-02-12 19:05:40 +01:00
b7b4dad1a5 Simplified return code 2025-02-12 17:34:48 +01:00
3fb05c382a Reduced number of register moves 2025-02-12 17:02:51 +01:00
b32df45289 Removed unnecessary function 2025-02-12 16:29:45 +01:00
be384c5136 Fixed incorrect number of history entries 2025-02-12 15:00:19 +01:00
c10395eddc Fixed volatile registers on external calls 2025-02-12 12:47:08 +01:00
49afa5d800 Moved external calls to a separate function 2025-02-12 11:25:20 +01:00
3b66dae1d4 Implemented extern functions 2025-02-12 00:04:30 +01:00
1083db6ab2 Added more tests 2025-02-11 18:48:40 +01:00
b2f67429f3 Improved color support on Windows 2025-02-11 12:41:20 +01:00
4dd40f6fec Added number of used functions to the statistics 2025-02-10 17:17:59 +01:00
371059d08a Improved type system checks 2025-02-10 14:09:27 +01:00
b2a9dc3aa7 Added funding information 2025-02-10 11:16:22 +01:00
2b46e68f6f Improved documentation 2025-02-10 10:56:32 +01:00
f19d9063a5 Improved type system 2025-02-09 23:52:07 +01:00
7634244c56 Added array type 2025-02-09 20:25:37 +01:00
c69f1aab5c Completed basic support for Windows 2025-02-09 16:31:16 +01:00
01ea543995 Implemented console output for Windows 2025-02-09 16:24:36 +01:00
064bb3acc7 Added a zero byte at the end of strings 2025-02-09 15:37:00 +01:00
b1228be79a Fixed incorrect parameters for memory deallocation 2025-02-09 15:25:25 +01:00
ade1fd8fff Fixed incorrect memory allocations 2025-02-09 14:38:01 +01:00
2b2e707520 Implemented length storage of allocated memory 2025-02-09 14:14:41 +01:00
1ff56e0856 Implemented network byte order conversion 2025-02-08 23:59:19 +01:00
9002116ee4 Fixed server example on Mac 2025-02-08 19:09:36 +01:00
d0d096bcd2 Improved server example 2025-02-08 17:34:11 +01:00
526385280a Improved error handling for struct types 2025-02-08 16:29:56 +01:00
97cdcbd1cb Implemented package specific structs 2025-02-08 16:06:39 +01:00
91bafc0867 Added generic types to sizeof 2025-02-08 14:44:13 +01:00
01342f5318 Removed unnecessary type conversions 2025-02-08 13:02:18 +01:00
feacc27f49 Added more tests 2025-02-08 00:40:41 +01:00
1d50720c28 Updated dependencies 2025-02-08 00:26:50 +01:00
cba26d8154 Added more tests 2025-02-07 21:25:29 +01:00
5fc9cc4dff Simplified CLI errors 2025-02-07 20:08:49 +01:00
c2d1084434 Simplified code 2025-02-07 19:41:54 +01:00
63a2752c56 Implemented struct size calculation after a scan 2025-02-07 16:26:24 +01:00
c28eace966 Renamed arm64 to arm 2025-02-07 11:38:21 +01:00
78aee7999b Added asmc package 2025-02-06 23:26:10 +01:00
cae3092df7 Improved server example 2025-02-06 17:11:57 +01:00
94a0e81920 Added HTTP server example 2025-02-06 14:46:33 +01:00
9dcd43be46 Reorganized sys functions 2025-02-06 13:46:37 +01:00
170931cf5d Added socket syscalls for Mac 2025-02-06 13:19:04 +01:00
ace2f3c4a5 Allowed comments in the scanner 2025-02-06 13:10:44 +01:00
f36b1f6c7c Added server example 2025-02-06 12:49:17 +01:00
d4f9071ee4 Renamed x64 to x86 2025-02-05 23:16:18 +01:00
536 changed files with 10685 additions and 5411 deletions

View File

@ -1,35 +1,67 @@
# 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
- Small executables
- Zero dependencies
## Installation
```shell
git clone https://git.akyoto.dev/cli/q
git clone https://git.urbach.dev/cli/q
cd q
go build
```
Either use `./q` from the build directory or get access to the shorter `q` in any directory with `ln -s $PWD/q ~/.local/bin/q`.
## Usage
```shell
./q run examples/hello
q run examples/hello
```
Builds an executable from `examples/hello` and runs it.
You can take a look at the [examples](../examples).
## Links
## Tests
- [Chat](https://matrix.to/#/#community:akyoto.dev)
- [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st)
- [License](https://akyoto.dev/license)
- [Todo](todo.md)
```shell
go run gotest.tools/gotestsum@latest
```
This will run over 400 [tests](../tests) in various categories.
## Platforms
You can cross-compile executables for Linux, Mac and Windows.
```shell
q build examples/hello --os linux
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. You can help by donating via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang).
## License
Please see the [license documentation](https://urbach.dev/license).
## Copyright

View File

@ -17,20 +17,28 @@
- [x] Hexadecimal, octal and binary literals
- [x] Escape sequences
- [x] Multiple return values
- [ ] Type system
- [x] Data structures
- [x] Type system
- [ ] Dynamic libraries: Linux Mac ~~Windows~~
- [ ] Unsigned types
- [ ] Type operator `?`
- [ ] Data structures
- [ ] Slices
- [ ] Floating-point arithmetic
- [ ] Error handling
- [ ] Threading library
- [ ] Self-hosted compiler
### Library
- [ ] Efficient memory allocator
- [ ] Error handling
- [ ] Multithreading: ~~Linux~~ Mac ~~Windows~~
### Keywords
- [x] `assert`
- [x] `const`
- [x] `else`
- [ ] `for`
- [x] `extern`
- [x] `for`
- [x] `if`
- [x] `import`
- [x] `loop`
@ -41,6 +49,7 @@
- [x] Exclude unused functions
- [x] Constant folding
- [ ] SSA form
- [ ] Constant propagation
- [ ] Function call inlining
- [ ] Loop unrolls
@ -69,12 +78,12 @@
### Architecture
- [ ] arm64
- [ ] arm
- [ ] riscv
- [x] x64
- [x] x86
### Platform
- [x] Linux
- [x] Mac
- [ ] Windows
- [x] Windows

View File

@ -1,14 +1,13 @@
import io
import mem
import sys
main() {
length := 5
address := mem.alloc(length)
address[0] = 'H'
address[1] = 'e'
address[2] = 'l'
address[3] = 'l'
address[4] = 'o'
sys.write(1, address, length)
mem.free(address, length)
buffer := mem.alloc(5)
buffer[0] = 'H'
buffer[1] = 'e'
buffer[2] = 'l'
buffer[3] = 'l'
buffer[4] = 'o'
io.write(buffer)
mem.free(buffer)
}

View File

@ -1,11 +1,11 @@
import log
import sys
import fmt
import io
main() {
collatz(12)
}
collatz(x Int) {
collatz(x int) {
loop {
if x & 1 == 0 {
x /= 2
@ -13,12 +13,12 @@ collatz(x Int) {
x = 3 * x + 1
}
log.number(x)
fmt.decimal(x)
if x == 1 {
return
}
sys.write(1, " ", 1)
io.write(" ")
}
}

View File

@ -9,7 +9,7 @@ main() {
n := sys.read(0, address, length)
if n <= 0 {
mem.free(address, length)
mem.free(address)
return
}

View File

@ -1,10 +1,10 @@
import log
import fmt
main() {
log.number(factorial(5))
fmt.decimal(factorial(5))
}
factorial(x Int) -> Int {
factorial(x int) -> int {
if x <= 1 {
return 1
}

View File

@ -1,10 +1,10 @@
import log
import fmt
main() {
log.number(fibonacci(10))
fmt.decimal(fibonacci(10))
}
fibonacci(x Int) -> Int {
fibonacci(x int) -> int {
if x <= 1 {
return x
}

View File

@ -1,19 +1,19 @@
import log
import sys
import fmt
import io
main() {
fizzbuzz(15)
}
fizzbuzz(n Int) {
fizzbuzz(n int) {
x := 1
loop {
switch {
x % 15 == 0 { print("FizzBuzz", 8) }
x % 5 == 0 { print("Buzz", 4) }
x % 3 == 0 { print("Fizz", 4) }
_ { log.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
@ -22,10 +22,6 @@ fizzbuzz(n Int) {
return
}
print(" ", 1)
io.write(" ")
}
}
print(address Pointer, length Int) {
sys.write(1, address, length)
}

View File

@ -1,10 +1,10 @@
import log
import fmt
main() {
log.number(gcd(1071, 462))
fmt.decimal(gcd(1071, 462))
}
gcd(a Int, b Int) -> Int {
gcd(a int, b int) -> int {
loop {
switch {
a == b { return a }

View File

@ -1,9 +1,5 @@
import sys
import io
main() {
print("Hello\n", 6)
}
print(address Pointer, length Int) {
sys.write(1, address, length)
io.write("Hello\n")
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import log
import sys
import fmt
import io
main() {
n := 100
@ -10,36 +10,36 @@ main() {
return
}
if isPrime(i) == 1 {
if isPrime(i) {
if i != 2 {
sys.write(1, " ", 1)
io.write(" ")
}
log.number(i)
fmt.decimal(i)
}
i += 1
}
}
isPrime(x Int) -> Int {
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)
}

35
examples/server/server.q Normal file
View File

@ -0,0 +1,35 @@
import io
import net
import sys
main() {
socket := sys.socket(2, 1, 0)
if socket < 0 {
io.write("socket error\n")
sys.exit(1)
}
if net.bind(socket, 8080) != 0 {
io.write("bind error\n")
sys.exit(1)
}
if sys.listen(socket, 128) != 0 {
io.write("listen error\n")
sys.exit(1)
}
io.write("listening...\n")
loop {
conn := sys.accept(socket, 0, 0)
if conn >= 0 {
io.writeTo(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n")
sys.close(conn)
} else {
io.write("accept error\n")
}
}
}

13
examples/shell/const.q Normal file
View File

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

View File

@ -1,29 +1,27 @@
import io
import mem
import sys
main() {
length := 256
command := mem.alloc(length)
argv := mem.alloc(1)
envp := mem.alloc(1)
info := mem.alloc(24)
loop {
sys.write(1, "$ ", 2)
n := sys.read(0, command, length)
io.write("λ ")
n := io.read(command)
if n <= 0 {
return
}
command[n-1] = '\0'
pid := sys.fork()
command[n-1] = 0
pid := sys.clone(sig.chld, 0, 0, 0, 0)
if pid == 0 {
sys.execve(command, argv, envp)
sys.execve(command, 0, 0)
return
}
sys.waitid(0, pid, info, 4)
sys.waitid(idtype.pid, pid, 0, state.exited)
}
}

View File

@ -1,17 +1,15 @@
import sys
import io
import thread
import time
main() {
thread.create(work)
thread.create(work)
thread.create(work)
for 0..3 {
thread.create(work)
}
work()
}
work() {
sys.write(1, "[ ] start\n", 10)
time.sleep(10 * 1000 * 1000)
sys.write(1, "[x] end\n", 8)
sys.exit(0)
io.write("[ ] start\n")
io.write("[x] end\n")
}

View File

@ -1,10 +1,11 @@
import mem
main() {
text := mem.alloc(4)
text[0] = 'H'
text[1] = 'i'
text[2] = '!'
user32.MessageBoxA(0, text, text, 0x00240040)
mem.free(text, 4)
title := "Title."
text := "Hi!"
user32.MessageBoxA(0, text, title, 0x240040)
}
extern {
user32 {
MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int
}
}

10
go.mod
View File

@ -1,10 +1,10 @@
module git.akyoto.dev/cli/q
module git.urbach.dev/cli/q
go 1.23
go 1.24
require (
git.akyoto.dev/go/assert v0.1.3
git.akyoto.dev/go/color v0.1.1
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12
)
require golang.org/x/sys v0.29.0 // indirect
require golang.org/x/sys v0.32.0 // indirect

12
go.sum
View File

@ -1,6 +1,6 @@
git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8=
git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM=
git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE=
git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE=
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo=
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A=
git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

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

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

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

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

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

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

28
lib/core/core_windows.q Normal file
View File

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

25
lib/fmt/fmt.q Normal file
View File

@ -0,0 +1,25 @@
import mem
import sys
decimal(x int) {
buffer := mem.alloc(20)
address, count := itoa(x, buffer)
sys.write(1, address, count)
mem.free(buffer)
}
itoa(x int, buffer []byte) -> (*byte, int) {
end := buffer + len(buffer)
tmp := end
digit := 0
loop {
x, digit = x / 10
tmp -= 1
tmp[0] = '0' + digit
if x == 0 {
return tmp, end - tmp
}
}
}

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
}

7
lib/io/io.q Normal file
View File

@ -0,0 +1,7 @@
const {
std {
in 0
out 1
err 2
}
}

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

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

View File

@ -1,26 +0,0 @@
import mem
import sys
number(x Int) {
length := 20
buffer := mem.alloc(length)
address, count := itoa(x, buffer, length)
sys.write(1, address, count)
mem.free(buffer, length)
}
itoa(x Int, buffer Pointer, length Int) -> (Pointer, Int) {
end := buffer + length
tmp := end
digit := 0
loop {
x, digit = x / 10
tmp -= 1
tmp[0] = '0' + digit
if x == 0 {
return tmp, end - tmp
}
}
}

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

@ -1,5 +0,0 @@
import sys
alloc(length Int) -> Pointer {
return sys.mmap(0, length, 0x1|0x2, 0x02|0x20)
}

View File

@ -1,5 +0,0 @@
import sys
alloc(length Int) -> Pointer {
return sys.mmap(0, length, 0x1|0x2, 0x02|0x1000)
}

12
lib/mem/alloc_unix.q Normal file
View File

@ -0,0 +1,12 @@
import sys
alloc(length int) -> []byte {
x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous)
if x < 0x1000 {
return x
}
[x] = length
return x + 8
}

View File

@ -1,5 +1,16 @@
import sys
alloc(length int) -> []byte {
x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite)
alloc(length Int) -> Pointer {
return sys.mmap(0, length, 0x0004, 0x3000)
if x < 0x1000 {
return x
}
[x] = length
return x + 8
}
extern {
kernel32 {
VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any
}
}

11
lib/mem/const_linux.q Normal file
View File

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

11
lib/mem/const_mac.q Normal file
View File

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

11
lib/mem/const_windows.q Normal file
View File

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

View File

@ -1,5 +0,0 @@
import sys
free(address Pointer, length Int) -> Int {
return sys.munmap(address, length)
}

5
lib/mem/free_unix.q Normal file
View File

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

9
lib/mem/free_windows.q Normal file
View File

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

3
lib/net/htons.q Normal file
View File

@ -0,0 +1,3 @@
htons(num uint16) -> uint16 {
return ((num & 0xFF) << 8) | (num >> 8)
}

10
lib/net/net_linux.q Normal file
View File

@ -0,0 +1,10 @@
import sys
bind(socket int, port uint16) -> int {
addr := new(sys.sockaddr_in)
addr.sin_family = 2
addr.sin_port = htons(port)
err := sys.bind(socket, addr, 20)
delete(addr)
return err
}

10
lib/net/net_mac.q Normal file
View File

@ -0,0 +1,10 @@
import sys
bind(socket int, port uint16) -> int {
addr := new(sys.sockaddr_in_bsd)
addr.sin_family = 2
addr.sin_port = htons(port)
err := sys.bind(socket, addr, 16)
delete(addr)
return err
}

34
lib/sys/struct_linux.q Normal file
View File

@ -0,0 +1,34 @@
sockaddr_in {
sin_family int16
sin_port uint16
sin_addr int64
sin_zero int64
}
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
}

7
lib/sys/struct_mac.q Normal file
View File

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

View File

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

View File

@ -1,17 +1,19 @@
write(fd Int, address Pointer, length Int) -> Int {
// out numberOfBytesWritten
// out overlapped
return kernel32.WriteFile(fd, address, length)
read(fd int, buffer *byte, length int) -> int {
fd = kernel32.GetStdHandle(-10 - fd)
kernel32.ReadConsole(fd, buffer, uint32(length), 0)
return length
}
mmap(address Int, length Int, protection Int, flags Int) -> Pointer {
return kernel32.VirtualAlloc(address, length, flags, protection)
write(fd int, buffer *byte, length int) -> int {
fd = kernel32.GetStdHandle(-10 - fd)
kernel32.WriteConsoleA(fd, buffer, uint32(length), 0)
return length
}
munmap(address Pointer, length Int) -> Int {
return kernel32.VirtualFree(address, length, 0x4000)
}
exit(code Int) {
kernel32.ExitProcess(code)
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,9 +1,23 @@
import core
import sys
create(func Pointer) -> Int {
size := 4096
stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000)
rip := stack + size - 8
store(rip, 8, func)
return sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip)
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
sighand 0x800
parent 0x8000
thread 0x10000
io 0x80000000
}
}

View File

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

View File

@ -1,13 +1,13 @@
import sys
sleep(nanoseconds Int) {
sleep(nanoseconds int) {
seconds := 0
if nanoseconds >= 1000000000 {
seconds, nanoseconds = nanoseconds / 1000000000
}
duration := new(timespec)
duration := new(sys.timespec)
duration.seconds = seconds
duration.nanoseconds = nanoseconds
sys.nanosleep(duration)

View File

@ -1,4 +0,0 @@
struct timespec {
seconds Int
nanoseconds Int
}

View File

@ -3,7 +3,7 @@ package main
import (
"os"
"git.akyoto.dev/cli/q/src/cli"
"git.urbach.dev/cli/q/src/cli"
)
func main() {

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

59
src/arm/Registers.go Normal file
View File

@ -0,0 +1,59 @@
package arm
import "git.urbach.dev/cli/q/src/cpu"
const (
X0 cpu.Register = iota // Function arguments and return values [0-7]
X1
X2
X3
X4
X5
X6
X7
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 // Intra-procedure call scratch registers [16-17]
X17
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
X23
X24
X25
X26
X27
X28
FP // Frame pointer
LR // Link register
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 (
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,
}
)

12
src/arm/Registers_test.go Normal file
View File

@ -0,0 +1,12 @@
package arm_test
import (
"testing"
"git.urbach.dev/cli/q/src/arm"
"git.urbach.dev/go/assert"
)
func TestRegisters(t *testing.T) {
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)
}

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