Compare commits

...

57 commits
main ... v2

Author SHA1 Message Date
janis f4e833ac46
idk>>?? 2026-08-04 18:20:58 +02:00
janis ca9fd58bed
export shared code 2026-07-14 16:36:54 +02:00
janis 4690b3c7ec
as0 works 2026-07-13 23:33:07 +02:00
janis 2f7282577a
fixes 2026-07-09 14:17:15 +02:00
janis c48c4261da
parse, encode inst 2026-07-09 13:51:01 +02:00
janis c4f9eb0e55
parse operands 2026-07-07 18:14:14 +02:00
janis f87ab3afa4
parsing mem operand 2026-07-06 19:03:42 +02:00
janis 9228b7949b
parsing registers 2026-07-06 12:30:30 +02:00
janis 75d765c905
many fixes, mapcar 2026-07-05 08:47:29 +02:00
janis dd8d005e5a
fixup makefile, fix parse_cur_token 2026-07-05 05:15:46 +02:00
janis 26c219a2f7
comments 2026-07-05 04:50:03 +02:00
janis e287ace328
fix typeof aliasing the cons method 2026-07-05 01:58:04 +02:00
janis 02551fe706
fix typeof, progn, eq, debug printing recursing genv 2026-07-05 01:24:45 +02:00
janis 224174a418
fixes define 2026-07-05 00:33:38 +02:00
janis 4e0719f891
tests and can eval complex programs 2026-07-04 21:40:08 +02:00
janis 52950c255a
can evaluate simple expressions 2026-07-04 20:20:05 +02:00
janis 2b447b5517
atoms, eval, obj_eq 2026-07-03 05:14:06 +02:00
janis 8d4769ad2b
run tests sequentially to avoid races 2026-07-03 02:56:07 +02:00
janis 87be0c0c33
parsing, lifetime management, tests 2026-07-03 02:53:21 +02:00
janis 17baf01a9e
add reversing software to flake 2026-07-03 02:52:38 +02:00
janis bb7a81265b
allocator, testing, make recipe for lisp1 2026-07-01 02:16:34 +02:00
janis 728549c046
refactoring 2026-06-30 20:27:06 +02:00
janis 6eeeca1266
factor out num construction 2026-06-13 16:36:17 +02:00
janis f9767b9f72
working on reducing code size 2026-06-12 21:41:50 +02:00
janis 927b8517c5
can syscall! 2026-06-12 03:17:45 +02:00
janis b66b4f6668
fix let, let* 2026-06-12 00:22:18 +02:00
janis 5ee33f8cf6
define, lambda, eval works 2026-06-12 00:10:58 +02:00
janis f809090299
lambda, define, if, eval, let, let* 2026-06-11 17:31:05 +02:00
janis 13f4e2bfe4
string parsing/remove nil tag in favour of constant pointer/add char tag 2026-06-11 17:30:43 +02:00
janis 6df1d2ac9b
can do math 2026-06-10 14:15:36 +02:00
janis 07435f5839
parsing operands 2026-06-09 16:43:04 +02:00
janis 9bd36905af
can add numbers (integers) 2026-06-09 01:03:51 +02:00
janis 10a45ff7f2
first builtin function 2026-06-09 00:53:19 +02:00
janis 45ce196938
eval 2026-06-09 00:09:07 +02:00
janis a78d06a216
add debugger frontends 2026-06-08 17:13:04 +02:00
janis aacf009a14
tiny lisp impl 2026-06-08 17:12:58 +02:00
janis fbffd9f654
add pwndbg to flake 2026-06-07 00:12:11 +02:00
janis f49939b72b
parse registers 2026-06-07 00:11:58 +02:00
janis d54b7ba1a9
verify hex1 2026-06-05 08:19:25 +02:00
janis ff22a96106
makefile 2026-06-05 08:15:29 +02:00
janis c9c3f239c8
fixes 2026-06-05 08:15:21 +02:00
janis 654c14d92b
elf helper (linker?) program 2026-06-05 07:01:48 +02:00
janis 79a4c65df3
change formatting 2026-06-05 05:22:59 +02:00
janis 52d2466eb5
encode offsets 2026-06-05 05:19:57 +02:00
janis f6ffba2460
calculated offsets 2026-06-05 04:32:19 +02:00
janis 0c81162553
move hex1 to its own stage 2026-06-05 02:46:58 +02:00
janis 0d6ed8a318
change hex0 comments to be ; instead of # 2026-06-05 02:45:33 +02:00
janis c2934ffd50
hex1 assembly works 2026-06-05 02:42:44 +02:00
janis 7472eb1279
fix so nasm can compile for testing 2026-06-05 01:24:51 +02:00
janis 44d5e95970
asdf 2026-06-05 00:09:02 +02:00
janis 6f76735710
stage 1 hex 2026-06-04 23:22:49 +02:00
janis 6b6bacd9dd
works 2026-06-04 08:09:14 +02:00
janis 6f516bbb93
almost works! 2026-06-04 08:05:40 +02:00
janis a9d96063f6
fix jmp rel again 2026-06-04 07:44:45 +02:00
janis 855893ee09
fix jmp rels 2026-06-04 07:16:30 +02:00
janis bce8e8356f
hex0 stage 2026-06-04 05:57:39 +02:00
janis 00b56875f9
nix flake update 2026-06-04 03:26:50 +02:00
50 changed files with 13505 additions and 8 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
lang/target
lang/libcompiler/target
/target/
/stages/build/

View file

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1760524057,
"narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
"lastModified": 1780243769,
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
"type": "github"
},
"original": {
@ -35,6 +35,22 @@
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1769861202,
"narHash": "sha256-JwhbEHmbFFH754loOPbpjPHf9EAi+oMm2RsFXnlbt1g=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f214de98544a6acf0d9917ba265ac50849048fcb",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
@ -50,23 +66,95 @@
"type": "github"
}
},
"pwndbg": {
"inputs": {
"nixpkgs": "nixpkgs_2",
"pyproject-build-systems": "pyproject-build-systems",
"pyproject-nix": "pyproject-nix",
"uv2nix": "uv2nix"
},
"locked": {
"lastModified": 1780751340,
"narHash": "sha256-MPZ+qTH0xK0zCWefximm2rAje6Zr8outa6Sj5uP9bLw=",
"owner": "pwndbg",
"repo": "pwndbg",
"rev": "0ad76cb7cc74fc80d64520596429f2fff9865c62",
"type": "github"
},
"original": {
"owner": "pwndbg",
"repo": "pwndbg",
"type": "github"
}
},
"pyproject-build-systems": {
"inputs": {
"nixpkgs": [
"pwndbg",
"nixpkgs"
],
"pyproject-nix": [
"pwndbg",
"pyproject-nix"
],
"uv2nix": [
"pwndbg",
"uv2nix"
]
},
"locked": {
"lastModified": 1763662255,
"narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=",
"owner": "pyproject-nix",
"repo": "build-system-pkgs",
"rev": "042904167604c681a090c07eb6967b4dd4dae88c",
"type": "github"
},
"original": {
"owner": "pyproject-nix",
"repo": "build-system-pkgs",
"type": "github"
}
},
"pyproject-nix": {
"inputs": {
"nixpkgs": [
"pwndbg",
"nixpkgs"
]
},
"locked": {
"lastModified": 1769936401,
"narHash": "sha256-kwCOegKLZJM9v/e/7cqwg1p/YjjTAukKPqmxKnAZRgA=",
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"rev": "b0d513eeeebed6d45b4f2e874f9afba2021f7812",
"type": "github"
},
"original": {
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"pwndbg": "pwndbg",
"rust-overlays": "rust-overlays"
}
},
"rust-overlays": {
"inputs": {
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1760582142,
"narHash": "sha256-RSLRjAoS75szOc9fFzRi9/jzPbYsiqPISSLZTloaKtM=",
"lastModified": 1780456895,
"narHash": "sha256-CvRZn3Ut0scqLJ1xwQFkZwKGVBUUNBPrFVXRTMZpbfU=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "9ea094253b9389ba7dd4f18637f66b5824276d1d",
"rev": "7cc96a6a3fd6613cafd633250a3934483479b9a1",
"type": "github"
},
"original": {
@ -89,6 +177,31 @@
"repo": "default",
"type": "github"
}
},
"uv2nix": {
"inputs": {
"nixpkgs": [
"pwndbg",
"nixpkgs"
],
"pyproject-nix": [
"pwndbg",
"pyproject-nix"
]
},
"locked": {
"lastModified": 1769957392,
"narHash": "sha256-6PkqwwYf5K2CHi2V+faI/9pqjfz/HxUkI/MVid6hlOY=",
"owner": "pyproject-nix",
"repo": "uv2nix",
"rev": "d18bc50ae1c3d4be9c41c2d94ea765524400af75",
"type": "github"
},
"original": {
"owner": "pyproject-nix",
"repo": "uv2nix",
"type": "github"
}
}
},
"root": "root",

View file

@ -5,12 +5,16 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlays.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
pwndbg.url = "github:pwndbg/pwndbg";
};
outputs = { self, nixpkgs, flake-utils, rust-overlays, ...}:
outputs = { self, nixpkgs, flake-utils, rust-overlays, pwndbg, ...}:
flake-utils.lib.eachDefaultSystem (system: let
overlays = [
(import rust-overlays)
(final: prev: {
pwndbg = pwndbg.packages.${system}.default;
})
];
pkgs = import nixpkgs {
inherit system overlays;
@ -31,11 +35,17 @@
just-formatter
just-lsp
gdb
gdbgui
seer
rr
nasm
nasmfmt
git
rust
pkgs.pwndbg
radare2
iaito
];
};
});

65
lang/src/typeck.asm Normal file
View file

@ -0,0 +1,65 @@
default rel
%include "src/ast.inc"
section .text
extern vec_init_with
extern vec_push
extern vec_get
extern vec_insert_sorted
extern vec_get_or
extern panic
extern memcpy
extern strcmp
extern vec_binary_search_by
extern vec_insert
extern bump_alloc
;; rdi: *Ctx
;; rsi: *Ast
;; rdx: node
;; rcx: scope
;; define-fn fn typeck_for_each(ctx: *mut TypeCheckCtx, ast: *mut Ast, node_index: u64, scope: u64)
typeck_for_each:
push rbp
mov rbp, rsp
; start-structs
; struct TypeCheckCtx {
; type_graph: TCGraph,
; ast_to_type_map: Vec<(u64, u64)>,
; }
; struct TCEdge<E> {
; nodes: [u64; 2],
; next_edge: [u64; 2],
; weight: E,
; }
; struct TCNode<N> {
; next_edge: [u64; 2],
; weight: N,
; }
; struct TCGraph {
; types: Vec<TCNode<Type>>,
; type_vars: Vec<TCNode<()>>,
; edges: Vec<TCEdge<bool>>,
; }
; end-structs
; In the pre-pass, the typechecker graph is built according to the typing rules of each ast node.
; E.g., for a binary expression, both operands must be of the same type, and the result type must be a supertype of the operand type:
; E1(Add, E2(..), E3(..))
; E1: T1, E2: T2, E3: T3
; T2 == T3, T1 >= T2, T1 >= T3
; In a subsequent pass, the type-variable graph is simplified and equal type nodes are merged to make traversal easier.
; The resulting type graph should be a DAG.
sub rsp, 32
mov [rsp], rsi ; Ast
mov [rsp + 8], rdi ; TypeCheckCtx
mov [rsp + 16], rdx ; node_index
mov [rsp + 24], rcx ; scope
pop rbp
ret
;; } typeck_for_each

45
stages/Makefile Normal file
View file

@ -0,0 +1,45 @@
BUILD = build
all: $(BUILD)/stage1-hex1 $(BUILD)/stage1-elf $(BUILD)/stage0
stage0-hex0: $(BUILD)/stage0-hex0
# We need an initial compiler to convert the hex0.in file into an executable that can be used to generate the stage0 compiler.
$(BUILD)/stage0-bootstrap: hex0/hex0.in
for x in `sed '/^;/d;s/;.*//;s/ *//' hex0/hex0.in`; do printf \\x$$x; done > $@
chmod +x $@
# We use the stage0-bootstrap compiler to generate the stage0-hex0 compiler.
$(BUILD)/stage0-hex0: $(BUILD)/stage0-bootstrap
$< < hex0/hex0.in > $@
chmod +x $@
# Finally, we use the stage0-hex0 compiler to generate the stage0 compiler, which should be identical to the stage0-hex0 compiler.
$(BUILD)/stage0: $(BUILD)/stage0-hex0
$< < hex0/hex0.in > $@
chmod +x $@
verify-hex0: $(BUILD)/stage0
cmp $(BUILD)/stage0 $(BUILD)/stage0-hex0
$(BUILD)/stage1-elf: $(BUILD)/stage0
$< < hex1/elf.in > $@
chmod +x $@
$(BUILD)/stage1-hex1.text: $(BUILD)/stage0
$< < hex1/hex1.in > $@
$(BUILD)/stage1-hex1: $(BUILD)/stage1-elf $(BUILD)/stage1-hex1.text
$(BUILD)/stage1-elf $(BUILD)/stage1-hex1 $(BUILD)/stage1-hex1.text
chmod +x $@
verify-hex1: $(BUILD)/stage1-elf $(BUILD)/stage1-hex1
$(BUILD)/stage1-hex1 < hex1/helloworld.h1 > $(BUILD)/helloworld.text
$(BUILD)/stage1-elf $(BUILD)/helloworld.elf $(BUILD)/helloworld.text
chmod +x $(BUILD)/helloworld.elf
$(BUILD)/helloworld.elf
clean:
rm -rf $(BUILD)
mkdir -p $(BUILD)

18
stages/as0/Makefile Normal file
View file

@ -0,0 +1,18 @@
test.bin: test.rs as0.o
rustc -Clink-arg=-fuse-ld=mold -Clink-arg=as0.o --edition=2024 --test -g $< -o $@
as0.o: as0.asm
nasm -g -f elf64 -o as0.o as0.asm
test: test.bin
./test.bin
as0: as0.o
ld -m elf_x86_64 -e _entry_as0 -o as0 as0.o
print_table: as0.o
ld -m elf_x86_64 -e _entry_print_table -o print_table as0.o
clean:
rm -f as0.o test.bin as0 print_table

2723
stages/as0/as0.asm Normal file

File diff suppressed because it is too large Load diff

385
stages/as0/as1.asm Normal file
View file

@ -0,0 +1,385 @@
section .bss
token_buf resb 0x100
buf resb 0x100
labels resb 0x1000
section .text
extern panic_abort
;; returns the next character from the source without advancing.
extern peekc
;; returns the next character from the source and advances.
extern getc
;; advances past the next character and peeks the next.
extern consuming_peekc
;; calculates a quick hash of a byte sequence given by rdi (pointer) and rsi (length)
fasthash:
mov eax, 0x811c9dc5 ; FNV offset basis
.loop:
test rsi, rsi
jz .done
movzx ecx, byte [rdi] ; get next byte
xor eax, ecx
imul eax, 0x1000193 ; FNV prime
inc rdi
dec rsi
jmp .loop
.done:
ret
;; @param lhs: (rdi, rsi)
;; @param rhs: (rdx, rcx)
;; @return al
strcmp:
cmp rcx, rsi
cmovb rsi, rcx ; if rhs is shorter, use its length for the loop
xor eax, eax
.loop:
cmp rsi, rax
je .equal
movzx ecx, byte [rdx + rax]
cmp byte [rdi + rax], cl
lea rax, [rax + 1]
je .loop
seta al ; al = lhs > rhs
sbb al, 0 ; al = al - CF
ret
.equal:
xor eax, eax
ret
memcpy:
xor rax, rax
.loop:
cmp rax, rdx
je .done
mov al, byte [rdi + rax]
mov byte [rsi + rax], al
lea rax, [rax + 1]
jmp .loop
.done:
ret
;; returns 1 if the result of `peekc()` is $sil
;; treats all characters less than ' ' as spaces.
is_ch:
push rsi
call peekc
pop rsi
cmp al, ' '
setbe cl ; cl = al <= ' '
movzx ecx, cl
mov edx, ' '
mul ecx, edx ; cl = (al < ' ') ? ' ' : 0
cmp sil, ' '
cmove ax, cx ; al = (sil < ' ') ? cl : al
cmp al, sil
setz al ; al = al == sil
ret
;; struct packed LabelEntry {
;; hash: u64,
;; file_offset: Option<NonMaxU32>,
;; }
;; push the label pointed at by rdi with length rsi, and the current file offset onto the labels array.
push_lbl:
push r12
lea r12, [rel labels]
mov rax, dword [r12 + 0x1000 - 4] ; get current label count
cmp rax, 341
jge panic_abort
lea rax, [rax + rax*2]
shl rax, 2 ; $rax = num_labels + size_of::<label_entry>()
add r12, rax
call fasthash
mov qword [r12], rax ; store hash in labels array
mov dword [r12 + 8], -1 ; initialize file offset to -1 (invalid)
inc dword [r12 + 0x1000 - 4] ; increment label count
pop r12
ret
;; searches for the offset of the label with the name pointed at by rdi with length rsi, and returns it in rax. If the label is not found, returns 0 in rax.
find_lbl_ptr:
call fasthash
lea rdx, [r15 + 0x5010] ; rdx = pointer to start of labels array
mov ebx, dword [r15 + 0x6000]
.loop:
test rbx, rbx
jz .not_found
dec rbx
cmp qword [rdx], rax
je .found
add rdx, 12 ; move to next label entry
jmp .loop
.found:
mov rax, rdx
ret
.not_found:
xor rax, rax
ret
push_or_find_lbl:
call fasthash
push rax
mov rdi, rax
call find_lbl_ptr
test rax, rax
jz .push
add rsp, 8
ret
.push:
pop rdi
call push_lbl
ret
find_lbl_offset:
call find_lbl_ptr
test rax, rax
jz .not_found
mov eax, dword [rax + 8] ; get file offset of label
ret
.not_found:
xor rdi, rdi
call panic_abort
set_lbl_offset:
push rsi
call find_lbl_ptr
pop rsi
mov dword [rax + 8], esi ; set file offset of label
ret
is_alpha:
movzx edi, dil
and edi, 0x1fffdf ; ignore bit 5 (case)
add edi, -65 ; subtract 'A'
cmp edi, 26 ; check if in range 0-25
setb al
ret
is_digit:
movzx edi, dil
lea eax, [rdi - 48] ; subtract '0'
cmp eax, 10 ; check if in range 0-9
setb al
ret
;; returns 1 if $dil is a valid identifier character (alphanumeric, '-' or '_'), and 0 otherwise.
;; clobbers rdi, rax, rcx
is_id_cont:
movzx rdi, dil
; is_digit {
lea eax, [rdi - 48]
cmp eax, 10
setb al
; } || is_alpha {
mov ecx, edi
and ecx, 0x1fffdf
add ecx, -65
cmp ecx, 26
setb cl
; } is_alpha
or al, cl
cmp edi, 45 ; || '-'
sete cl
or al, cl
cmp edi, 95 ; || '_'
sete cl
or al, cl
ret
;; converts char $dil to a digit with radix $rsi, returning it in $edx.
;; $al is set to 1 if the char is a valid digit, and 0 otherwise.
to_digit:
lea eax, [rsi - 2]
cmp eax, 35
jae .invalid
movzx rdi, dil
lea edx, [rdi - 65] ; 'A' = 65
and edx, -33 ; convert to uppercase
add edx, 10 ; 'A' should map to 10
lea eax, [rdi - 48] ; '0' = 48
cmp esi, 11
cmovb edx, eax ; if radix <= 10, then take the difference from '0'
cmp edi, 58
cmovb edx, eax ; or if char < '9', then take the difference from '0'
xor eax, eax
cmp edx, esi
setb al ; al = edx < radix
ret
.invalid:
xor eax, eax
ret
;; reads from $rdi and parses digits with radix $rsi until a non-digit is encountered.
parse_digits:
sub rsp, 24
mov qword [rsp + 16], rsi ; save the radix
mov qword [rsp + 8], rdi ; save the source iterator
mov qword [rsp], 0
.loop:
mov rdi, qword [rsp + 8] ; restore the source iterator
call peekc
mov dil, al
mov rsi, qword [rsp + 16] ; restore the radix
call to_digit
test al, al
jz .done
mov rax, qword [rsp]
mov rsi, qword [rsp + 16] ; restore the radix
imul rax, rsi
add rax, rdx
mov qword [rsp], rax
mov rdi, qword [rsp + 8] ; restore the source iterator
call getc
jmp .loop
.done:
mov rax, [rsp]
add rsp, 24
ret
parse_num:
sub rsp, 24
mov qword [rsp], 0 ; acc
mov qword [rsp + 8], rdi ; source iterator
mov dword [rsp + 16], 10 ; radix
call peekc
cmp al, `-`
jne .skip_sign
mov qword [rsp], -1 ; acc = -1
call consuming_peekc
.skip_sign:
cmp al, `0`
jne .skip_radix
call consuming_peekc
cmp al, `x`
jne .skip_radix
mov dword [rsp + 16], 16 ; radix = 16
call consuming_peekc
.skip_radix:
mov dil, al
call to_digit
test al, al
jz .done
mov rax, qword [rsp] ; acc
mov rsi, qword [rsp + 16] ; radix
mov rcx, rdx
imul rax, rsi
add rax, rcx
mov qword [rsp], rax ; acc = acc * radix + digit
mov rdi, qword [rsp + 8] ; source iterator
call consuming_peekc
jmp .skip_radix
.done:
mov rax, qword [rsp] ; move the result into rax
add rsp, 24
ret
parse_label:
push r12
lea r12, [rel buf]
sub rsp, 8
mov qword [rsp], rdi ; source iterator
call consuming_peekc
mov dil, al
call is_alpha
test al, al
jz .done
mov byte [r12], al
inc r12
mov rdi, qword [rsp] ; restore source iterator
call consuming_peekc
mov dil, al
call is_id_cont
test al, al
jz .done
jmp .loop
.done:
lea rdi, [rel buf]
mov rsi, r12
sub rsi, rdi
call push_or_find_lbl
add rsp, 8
pop r12
ret
;; reads the next token into `token_buf`
next_token:
sub rsp, 8
mov qword [rsp], 0
.skip_spaces:
mov rsi, ' '
call is_ch
test al, al
jz .skipped_spaces:
call getc
jmp .skip_spaces
.skipped_spaces:
; leading [ is a memory access
mov rsi, `[`
call is_ch
test al, al
jnz .mem_access
; leading @ is label-def
mov rsi, '@'
call is_ch
test al, al
jnz .label_def
; leading ' is a label-ref
mov rsi, `'`
call is_ch
test al, al
jnz .label_ref
; leading digit is a number
call peekc
mov rdi, rax
call is_digit
test al, al
jnz .number
; leading ; or # is a comment
mov rsi, `;`
call is_ch
test al, al
jnz .comment
mov rsi, `#`
call is_ch
test al, al
jnz .comment
; leading , is a separator
mov rsi, `,`
call is_ch
test al, al
jz .ident
; just skip the comma since its not important for parsing
call getc
jmp .skip_spaces
.ident:
; now we have some identifier
; (mov rax (byte rdi))
;; registers:
;; r0/rax
;; r1/rcx
;; r2/rdx
;; r3/rbx
;; r4/rsp
;; r5/rbp
;; r6/rsi
;; r7/rdi
;; r8
;; r9
;; r10
;; r11
;; r12
;; r13
;; r14
;; r15
;;
;; registers of the form `rNUM` may have an optional suffix b, w, d, or q
;; legacy registers may also be referenced by their 8-bit, 16-bit, or 32-bit names (e.g. al, ax, eax, rax or spl, sp, esp, rsp)
;; high-byte registers (ah, bh, ch, dh) are not supported

415
stages/as0/test.rs Normal file
View file

@ -0,0 +1,415 @@
unsafe extern "C" {
// unsafe fn parse_gpr(src: *mut Source, prefix: u8) -> (u64, u64);
// unsafe fn parse_reg(src: *mut Source) -> (u64, u64);
unsafe fn try_parse_reg(_: *const u8) -> (usize, Register);
unsafe fn try_parse_mem(_: *const u8, _: *mut Operand) -> (usize, *const Operand);
#[link_name = "try_parse_operand"]
unsafe fn try_parse_operand_impl(_: *const u8, _: *mut Operand) -> (usize, *const Operand);
#[link_name = "try_parse_inst"]
unsafe fn try_parse_inst_impl(_: *const u8, _: *mut Instruction)
-> (usize, *const Instruction);
#[link_name = "encode_inst"]
unsafe fn encode_inst_impl(_: *mut Instruction) -> usize;
unsafe fn init_label_tables();
unsafe fn fasthash(_: *const u8, _: usize) -> u32;
#[link_name = "buf"]
static mut BUF: [u8; 0x100];
}
#[repr(C)]
#[derive(Debug, PartialEq, Eq)]
struct Register {
size: u32,
num: u32,
}
#[repr(u32)]
#[derive(Debug, PartialEq, Eq)]
enum OperandKind {
None = 0,
Reg = 1,
Mem = 2,
Imm = 4,
MemLabel = 2 | 8,
ImmLabel = 4 | 8,
}
#[repr(C, align(8))]
#[derive(Debug, PartialEq, Eq)]
struct Operand {
kind: OperandKind,
size: u32,
reg: Register,
index: Register,
scale: u32,
disp: i32,
}
impl Operand {
fn parse(bytes: &[u8]) -> Option<(usize, Self)> {
let mut op = core::mem::MaybeUninit::<Operand>::uninit();
let (n, ptr) = unsafe { try_parse_operand_impl(bytes.as_ptr(), op.as_mut_ptr()) };
if n == 0 {
None
} else {
Some((n, unsafe { op.assume_init() }))
}
}
fn label(name: &[u8]) -> Self {
let mut this = Self {
kind: OperandKind::ImmLabel,
size: 2,
reg: Register::invalid(),
index: Register::invalid(),
scale: 0,
disp: 0,
};
unsafe {
let hash = fasthash(name.as_ptr(), name.len());
(&raw mut this).byte_add(8).cast::<u32>().write(hash as u32);
}
this
}
fn imm(value: i64) -> Self {
let size = if value.abs() == 0 {
value as u32
} else {
unsafe {
(value.abs() as u64)
.checked_ilog2()
.unwrap_unchecked()
.saturating_sub(1)
.checked_ilog2()
.unwrap_or(0)
+ 1
}
};
let mut this = Self {
kind: OperandKind::Imm,
size,
reg: Register::invalid(),
index: Register::invalid(),
scale: 0,
disp: 0,
};
unsafe {
(&raw mut this).byte_add(8).cast::<i64>().write(value);
}
this
}
}
#[repr(C)]
#[derive(Debug, PartialEq, Eq)]
struct Instruction {
mnemonic_offset: i32,
num_operands: u32,
operands: [Operand; 2],
}
impl Instruction {
fn parse(bytes: &[u8]) -> Option<(usize, Self)> {
let mut inst = core::mem::MaybeUninit::<Instruction>::uninit();
let (n, ptr) = unsafe { try_parse_inst_impl(bytes.as_ptr(), inst.as_mut_ptr()) };
if n == 0 {
None
} else {
Some((n, unsafe { inst.assume_init() }))
}
}
}
impl Register {
fn new(num: u32, size: u32) -> Self {
Self {
num,
size: size.ilog2(),
}
}
fn invalid() -> Self {
Self { num: !0, size: !0 }
}
fn rbp() -> Self {
Self { num: 5, size: 0 }
}
}
static mut TABLES_INIT: std::cell::LazyCell<()> =
std::cell::LazyCell::new(|| unsafe { init_label_tables() });
#[cfg(test)]
mod tests {
use super::*;
use core::mem::MaybeUninit;
fn write_to_buf(b: &[u8]) {
assert!(b.len() <= 0x100, "Buffer overflow");
unsafe {
core::ptr::copy_nonoverlapping(b.as_ptr(), (&raw mut BUF).cast(), b.len());
}
}
#[test]
fn parse_inst() {
_ = unsafe { *TABLES_INIT };
let res = Instruction::parse(b"mov byte ['rax], 4\0");
// assert_eq!(
// res,
// Some((
// 12,
// Instruction {
// mnemonic_offset: 0,
// num_operands: 2,
// operands: [
// Operand {
// kind: OperandKind::Reg,
// size: 3,
// reg: Register::new(0, 8),
// index: Register::invalid(),
// scale: 0,
// disp: 0,
// },
// Operand {
// kind: OperandKind::Reg,
// size: 3,
// reg: Register::new(3, 8),
// index: Register::invalid(),
// scale: 0,
// disp: 0,
// },
// ],
// }
// ))
// );
unsafe {
let mut inst = res.unwrap().1;
encode_inst_impl((&raw mut inst).cast());
}
}
#[test]
fn parse_mem() {
let mut op = MaybeUninit::<Operand>::uninit();
let (n, _) =
unsafe { try_parse_mem(b"qword [rax + rbx * 2 + 0x10]\0".as_ptr(), op.as_mut_ptr()) };
assert_eq!(
(n, unsafe { op.assume_init_ref() }),
(
28,
&Operand {
kind: OperandKind::Mem,
size: 3,
reg: Register::new(0, 8),
index: Register::new(3, 8),
scale: 2,
disp: 0x10,
}
)
);
let (n, _) =
unsafe { try_parse_mem(b"byte [r12 + r13 * 4 - 0x20]\0".as_ptr(), op.as_mut_ptr()) };
assert_eq!(
(n, unsafe { op.assume_init_ref() }),
(
27,
&Operand {
kind: OperandKind::Mem,
size: 0,
reg: Register::new(12, 8),
index: Register::new(13, 8),
scale: 4,
disp: -0x20,
}
)
);
let (n, _) = unsafe { try_parse_mem(b"word [r8 * 8 + 5]\0".as_ptr(), op.as_mut_ptr()) };
assert_eq!(
(n, unsafe { op.assume_init_ref() }),
(
17,
&Operand {
kind: OperandKind::Mem,
size: 1,
reg: Register::rbp(),
index: Register::new(8, 8),
scale: 8,
disp: 5,
}
)
);
}
#[test]
fn parse_operand() {
let cases = [
(
&b"rax\0"[..],
Operand {
kind: OperandKind::Reg,
size: 3,
reg: Register::new(0, 8),
index: Register::invalid(),
scale: 0,
disp: 0,
},
),
(
&b"qword [rax + rbx * 2 + 0x10]\0"[..],
Operand {
kind: OperandKind::Mem,
size: 3,
reg: Register::new(0, 8),
index: Register::new(3, 8),
scale: 2,
disp: 0x10,
},
),
(
&b"byte [r12 + r13 * 4 - 0x20]\0"[..],
Operand {
kind: OperandKind::Mem,
size: 0,
reg: Register::new(12, 8),
index: Register::new(13, 8),
scale: 4,
disp: -0x20,
},
),
(
&b"word [r8 * 8 + 5]\0"[..],
Operand {
kind: OperandKind::Mem,
size: 1,
reg: Register::rbp(),
index: Register::new(8, 8),
scale: 8,
disp: 5,
},
),
(&b"0x12345678\0"[..], Operand::imm(0x12345678)),
(&b"-0x12345678\0"[..], Operand::imm(-0x12345678)),
(&b"0\0"[..], Operand::imm(0)),
(&b"-1\0"[..], Operand::imm(-1)),
(&b"1\0"[..], Operand::imm(1)),
(&b"255\0"[..], Operand::imm(255)),
(&b"-255\0"[..], Operand::imm(-255)),
(&b"'wazzaah\0"[..], Operand::label(b"wazzaah")),
(
&b"qword [rsp + 'hii]"[..],
Operand {
kind: OperandKind::MemLabel,
size: 3,
reg: Register::new(4, 8),
index: Register::invalid(),
scale: 0,
disp: unsafe { fasthash(b"hii".as_ptr(), 3) as i32 },
},
),
];
for (text, expected) in cases {
let (n, result) = Operand::parse(text).expect("Failed to parse operand");
assert_eq!(
result,
expected,
"Failed to parse '{}'",
std::str::from_utf8(text).unwrap()
);
}
}
#[test]
fn parse_reg() {
let cases = [
(&b"rax\0"[..], (0, 8)),
(&b"rcx\0"[..], (1, 8)),
(&b"rdx\0"[..], (2, 8)),
(&b"rbx\0"[..], (3, 8)),
(&b"rsp\0"[..], (4, 8)),
(&b"rbp\0"[..], (5, 8)),
(&b"rsi\0"[..], (6, 8)),
(&b"rdi\0"[..], (7, 8)),
(&b"r8\0"[..], (8, 8)),
(&b"r9\0"[..], (9, 8)),
(&b"r10\0"[..], (10, 8)),
(&b"r11\0"[..], (11, 8)),
(&b"r12\0"[..], (12, 8)),
(&b"r13\0"[..], (13, 8)),
(&b"r14\0"[..], (14, 8)),
(&b"r15\0"[..], (15, 8)),
(&b"rip\0"[..], (!0, 8)),
(&b"eax\0"[..], (0, 4)),
(&b"ecx\0"[..], (1, 4)),
(&b"edx\0"[..], (2, 4)),
(&b"ebx\0"[..], (3, 4)),
(&b"esp\0"[..], (4, 4)),
(&b"ebp\0"[..], (5, 4)),
(&b"esi\0"[..], (6, 4)),
(&b"edi\0"[..], (7, 4)),
(&b"r8d\0"[..], (8, 4)),
(&b"r9d\0"[..], (9, 4)),
(&b"r10d\0"[..], (10, 4)),
(&b"r11d\0"[..], (11, 4)),
(&b"r12d\0"[..], (12, 4)),
(&b"r13d\0"[..], (13, 4)),
(&b"r14d\0"[..], (14, 4)),
(&b"r15d\0"[..], (15, 4)),
(&b"r8w\0"[..], (8, 2)),
(&b"r9w\0"[..], (9, 2)),
(&b"r10w\0"[..], (10, 2)),
(&b"r11w\0"[..], (11, 2)),
(&b"r12w\0"[..], (12, 2)),
(&b"r13w\0"[..], (13, 2)),
(&b"r14w\0"[..], (14, 2)),
(&b"r15w\0"[..], (15, 2)),
(&b"r8b\0"[..], (8, 1)),
(&b"r9b\0"[..], (9, 1)),
(&b"r10b\0"[..], (10, 1)),
(&b"r11b\0"[..], (11, 1)),
(&b"r12b\0"[..], (12, 1)),
(&b"r13b\0"[..], (13, 1)),
(&b"r14b\0"[..], (14, 1)),
(&b"r15b\0"[..], (15, 1)),
(&b"ax\0"[..], (0, 2)),
(&b"cx\0"[..], (1, 2)),
(&b"dx\0"[..], (2, 2)),
(&b"bx\0"[..], (3, 2)),
(&b"sp\0"[..], (4, 2)),
(&b"bp\0"[..], (5, 2)),
(&b"si\0"[..], (6, 2)),
(&b"di\0"[..], (7, 2)),
(&b"al\0"[..], (0, 1)),
(&b"cl\0"[..], (1, 1)),
(&b"dl\0"[..], (2, 1)),
(&b"bl\0"[..], (3, 1)),
(&b"spl\0"[..], (4, 1)),
(&b"bpl\0"[..], (5, 1)),
(&b"sil\0"[..], (6, 1)),
(&b"dil\0"[..], (7, 1)),
];
for (text, (reg, size)) in cases {
let (n, result) = unsafe { try_parse_reg(text.as_ptr()) };
assert_eq!(
(n, result),
(text.len() - 1, Register::new(reg, size)),
"Failed to parse '{}' n={n}",
std::str::from_utf8(text).unwrap()
);
}
}
}

View file

@ -0,0 +1,16 @@
@_start:
mov rdi, 0xa21444C52
push rdi
mov rdi, 0x4F57204F
shl rdi, 32
mov rsi, 0x4C4C4548
or rdi, rsi
push rdi
@init:
lea rsi, qword [rsp]
mov edx, 13
mov eax, 1
mov edi, 1
syscall
jmp 'init
jmp '_start

45
stages/as0/types.zig Normal file
View file

@ -0,0 +1,45 @@
const PackedOperandKind = enum(u3) {
reg = 0,
imm = 1,
mem = 2,
label = 3,
mem_label = 4,
};
const Register = enum(u4) {
rax = 0,
rbx = 1,
rcx = 2,
rdx = 3,
rsi = 4,
rdi = 5,
rsp = 6,
rbp = 7,
r8 = 8,
r9 = 9,
r10 = 10,
r11 = 11,
r12 = 12,
r13 = 13,
r14 = 14,
r15 = 15,
};
const PackedRegister = packed struct {
reg: Register,
size: u4,
};
const PackedOperand = union(PackedOperandKind) {
reg: PackedRegister,
imm: i64,
label: u32,
mem_label: u32,
mem: struct {
base: PackedRegister,
index: PackedRegister,
scale: u2,
disp: i32,
},
};

15
stages/hex0/Makefile Normal file
View file

@ -0,0 +1,15 @@
BUILD = ../build
all: $(BUILD)/stage0
stage0: $(BUILD)/stage0
stage0-bootstrap: $(BUILD)/stage0-bootstrap
$(BUILD)/stage0-bootstrap: hex0.in
for x in `sed '/^;/d;s/;.*//;s/ *//' hex0.in`; do printf \\x$$x; done > $@
chmod +x $@
$(BUILD)/stage0: $(BUILD)/stage0-bootstrap
$< < hex0.in > $@
chmod +x $@

221
stages/hex0/hex0.in Normal file
View file

@ -0,0 +1,221 @@
; ELF header
; e_ident: u128
; Magic number: 0x7f454c46 = "\x7fELF"
7f 45 4c 46
; EI_CLASS: u8 = ELFCLASS64
02
; EI_DATA: u8 = ELFDATA2LSB
01
; EI_VERSION: u8 = EV_CURRENT
01
; EI_OSABI: u8 = ELFOSABI_NONE
00
; EI_ABIVERSION: u8 = 0
00
; EI_PAD: [u8; 7]
00 00 00 00 00 00 00
; e_type: u16 = E_EXEC
02 00
; e_machine: u16 = EM_X86_64
3e 00
; e_version: u32 = EV_CURRENT
01 00 00 00
; e_entry: u64 = 0x400078
78 00 40 00 00 00 00 00
; e_phoff: u64 = 0x40
40 00 00 00 00 00 00 00
; e_shoff: u64 = 0x0
00 00 00 00 00 00 00 00
; e_flags: u32 = 0
00 00 00 00
; e_ehsize: u16 = 64
40 00
; e_phentsize: u16 = 56
38 00
; e_phnum: u16 = 1
01 00
; e_shentsize: u16 = 0
00 00
; e_shnum: u16 = 0
00 00
; e_shstrndx: u16 = 0
00 00
; End of ELF header
;
; Program header
; p_type: u32 = PT_LOAD
01 00 00 00
; p_flags: u32 = PF_R | PF_X
05 00 00 00
; p_offset: u64 = 0
00 00 00 00 00 00 00 00
; p_vaddr: u64 = 0x400000
00 00 40 00 00 00 00 00
; p_paddr: u64 = 0x0
00 00 00 00 00 00 00 00
; p_filesz: u64 = 0x167
67 01 00 00 00 00 00 00
; p_memsz: u64 = 0x167
67 01 00 00 00 00 00 00
; p_align: u64 = 0x1000
00 10 00 00 00 00 00 00
; End of program header
;
; Code segment (offset 0x40, virtual address 0x400000)
;
; _start=120:
; ; alloca 0x1000 bytes on the stack
; mov rbp, rsp
48 89 e5
; sub rsp, 0x10000, increase this if you want to read more than 0x10000 bytes
48 81 ec 00 00 01 00
; ; read 0x10000 bytes from stdin into the stack
; .READ: (130)
; xor rax, rax
48 31 c0
; mov edi, 0 (stdin)
48 c7 c7 00 00 00 00
; mov rsi, rsp
48 89 e6
; mov rdx, 0x10000, increase this if you want to read more than 0x10000 bytes
48 c7 c2 00 00 01 00
; syscall (sys_read)
0f 05
; cmp rax, 0
48 83 f8 00
; jle die
0f 8e b3 00 00 00
;# (162)
; ; end = r8
; mov r8, rax
49 89 c0
; add r8, rsp
49 01 e0
; ; pos = r9
; mov r9, rsp
49 89 e1
; ; dst = r10
; mov r10, rsp
49 89 e2
; add r10, 0x0000
49 81 c2 00 00 00 00
; ; while (pos < end) {
; ; .L0: (181)
; cmp r9, r8
4d 39 c1
; jge .L3
0f 8d 72 00 00 00
; (190)
;
; ; if (*pos == ';') {
; mov al , byte [r9]
41 8a 41 00
; cmp al, '\n'
3c 0a
; je .L2.1:
0f 84 15 00 00 00
; (202)
; cmp al, ';'
3c 3b
; jne .L1
0f 85 15 00 00 00
; ; while (*pos != '\n') pos++;
; ; .L2: (210)
; inc r9
49 ff c1
; cmp byte [r9], '\n'
41 80 39 0a
; jne .L2
0f 85 f3 ff ff ff
; .L2.1: (223)
; inc r9
49 ff c1
; jmp .L0
e9 ce ff ff ff
; (231)
; ; .L1: (231)
; ; convert *pos to high word
; mov al, byte [r9]
41 8a 01
; mov bl, al
88 c3
; sub bl, 'a'
80 eb 61
; add bl, 10
80 c3 0a
; sub al, '0'
2c 30
; cmp al, 10
3c 0a
; cmovge eax, ebx
0f 43 c3
; shl al, 4
c0 e0 04
; ; pos++
; inc r9
49 ff c1
; ; convert *pos to low word
; mov bl, byte [r9]
41 8a 19
; mov cl, bl
88 d9
; sub cl, 'a'
80 e9 61
; add cl, 10
80 c1 0a
; sub bl, '0'
80 eb 30
; cmp bl, 10
80 fb 0a
; cmovge ebx, ecx
0f 4d d9
; ; combine high and low word
; or al, bl
08 d8
; ; write the byte to dst
; mov byte [r10], al
41 88 02
; inc r10
49 ff c2
; inc r9
49 ff c1
; ; if (*pos++ == '\n') goto .L1;
; cmp byte [r9], '\n'
41 80 39 0a
; je .L0
0f 84 8d ff ff ff
;# (296)
; inc r9
49 ff c1
; jmp .L1 ;
e9 b7 ff ff ff
;# (304)
; ; }
; .L3: (304)
; ; write the decoded bytes to stdout
; mov rax, 1 (sys_write)
48 c7 c0 01 00 00 00
; mov rdi, 1 (stdout)
48 c7 c7 01 00 00 00
; mov rsi, rsp
48 89 e6
; add rsi, 0x000
48 81 c6 00 00 00 00
; mov rdx, r10
4c 89 d2
; sub rdx, rsi
48 29 f2
; syscall
0f 05
; jmp .READ ;
e9 2d ff ff ff
;# (341)
;
; die: (341)
; mov rax, 60 (sys_exit)
48 c7 c0 3c 00 00 00
; xor rdi, rdi
48 31 ff
; syscall
0f 05
; _start+215

150
stages/hex1/elf.asm Normal file
View file

@ -0,0 +1,150 @@
;; Program that takes an output file to which it writes elf64 headers and the contents of a second file which is assumed to be a text section.
die:
mov rax, 60 ; syscall: exit
mov rdi, 0 ; status: 0
syscall
ret
fopen:
mov rax, 2 ; syscall: open
mov rsi, 0 ; flags: O_RDONLY
mov rdx, 0 ; mode
syscall
ret
fopen_write:
mov rax, 2 ; syscall: open
mov rsi, 0x241 ; flags: O_WRONLY | O_CREAT | O_TRUNC
mov rdx, 0o644 ; mode: rw-r--r--
syscall
ret
;; rdi: fd
;; rsi: buffer
fstat:
mov rax, 5 ; syscall: fstat
syscall
ret
fsize:
push rdi
push rsi
call fstat
pop rsi
pop rdi
mov rax, [rsi + 48] ; st_size
ret
;; rdi: src
;; rsi: dst
;; rdx: len
memcpy:
.memcpy_loop:
test rdx, rdx
jz .memcpy_done
mov al, byte [rdi]
mov byte [rsi], al
inc rsi
inc rdi
dec rdx
jmp .memcpy_loop
.memcpy_done:
ret
;; variables:
;; +0 buffer: [u8; 0x1000]
;; +0x1000 bytes_read: u32
;; +0x1004 in_size: u32
;; +0x1008 in_fd: u32
;; +0x100c out_fd: u32
;; +0x1010 argc: u32
;; +0x1018 argv: *const *const u8
global _start
_start:
mov eax, dword [rsp] ; argc
lea rbx, [rsp + 8] ; argv
sub rsp, 0x4000 ; reserve stack space
mov r15, rsp ; r15 = pointer to our state
mov dword [r15 + 0x1010], eax ; store argc
mov qword [r15 + 0x1018], rbx ; store argv
cmp eax, 3
jl die
;; open output file
mov rdi, [r15 + 0x1018] ; rdi = argv
add rdi, 8 ; rdi = argv[1]
mov rdi, [rdi] ; rdi = pointer to argv[1]
call fopen_write
mov dword [r15 + 0x100c], eax ; store out_fd
;; open input file and get size
mov rdi, [r15 + 0x1018] ; rdi = argv
add rdi, 16 ; rdi = argv[2]
mov rdi, [rdi] ; rdi = pointer to argv[2]
call fopen
mov dword [r15 + 0x1008], eax ; store in_fd
mov edi, eax ; rdi = in_fd
lea rsi, [r15] ; rsi = pointer to statbuf
call fsize
mov dword [r15 + 0x1004], eax ; store in_size
;; copy elf header to scratch space
mov rdi, 0x400000
mov rsi, r15
mov rdx, 0x40
call memcpy
;; copy program header to scratch space
mov rdi, 0x400040
lea rsi, [r15 + 0x40]
mov rdx, 0x38
call memcpy
;; write p_filesz and p_memsz to 0x78 + in_size
mov rax, 0x78
add eax, dword [r15 + 0x1004] ; rax = 0x78 + in_size
mov dword [r15 + 0x60], eax ; write p_filesz
mov dword [r15 + 0x68], eax ; write p_memsz
;; write e_entry to point at 0x40 + 0x38 + in_size - 5
sub rax, 5
add rax, 0x400000
mov dword [r15 + 24], eax ; write e_entry
;; write Ehdr and Phdr to output file
mov edi, dword [r15 + 0x100c] ; rdi = out_fd
mov rsi, r15 ; rsi = buffer
mov rdx, 0x78 ; size of Ehdr + Phdr
mov rax, 1 ; syscall: write
syscall
;; copy text section from input file to output file
.copy_loop:
mov edi, dword [r15 + 0x1008] ; rdi = in_fd
mov rsi, r15 ; rsi = pointer to buffer
mov rdx, 0x1000 ; rdx = size of buffer
mov rax, 0 ; syscall: read
syscall
test rax, rax
jle die
mov dword [r15 + 0x1000], eax ; store number of bytes read
mov edi, dword [r15 + 0x100c] ; rdi = out_fd
mov rsi, r15 ; rsi = pointer to buffer
mov edx, dword [r15 + 0x1000] ; rdx = number of bytes read
mov rax, 1 ; syscall: write
syscall
cmp dword [r15 + 0x1000], 0x1000 ; if we read less than 0x1000 bytes, we are done
je .copy_loop
call die

269
stages/hex1/elf.in Normal file
View file

@ -0,0 +1,269 @@
; ELF header
; e_ident: u128
; Magic number: 0x7f454c46 = "\x7fELF"
7f 45 4c 46
; EI_CLASS: u8 = ELFCLASS64
02
; EI_DATA: u8 = ELFDATA2LSB
01
; EI_VERSION: u8 = EV_CURRENT
01
; EI_OSABI: u8 = ELFOSABI_NONE
00
; EI_ABIVERSION: u8 = 0
00
; EI_PAD: [u8; 7]
00 00 00 00 00 00 00
; e_type: u16 = E_EXEC
02 00
; e_machine: u16 = EM_X86_64
3e 00
; e_version: u32 = EV_CURRENT
01 00 00 00
; e_entry: u64 = 0x400078 + 0x5c
d4 00 40 00 00 00 00 00
; e_phoff: u64 = 0x40
40 00 00 00 00 00 00 00
; e_shoff: u64 = 0x0
00 00 00 00 00 00 00 00
; e_flags: u32 = 0
00 00 00 00
; e_ehsize: u16 = 64
40 00
; e_phentsize: u16 = 56
38 00
; e_phnum: u16 = 1
01 00
; e_shentsize: u16 = 0
00 00
; e_shnum: u16 = 0
00 00
; e_shstrndx: u16 = 0
00 00
; End of ELF header
;
; Program header
; p_type: u32 = PT_LOAD
01 00 00 00
; p_flags: u32 = PF_R | PF_X
05 00 00 00
; p_offset: u64 = 0
00 00 00 00 00 00 00 00
; p_vaddr: u64 = 0x400000
00 00 40 00 00 00 00 00
; p_paddr: u64 = 0x0
00 00 00 00 00 00 00 00
; p_filesz: u64 = 0x167
ef 01 00 00 00 00 00 00
; p_memsz: u64 = 0x167
ef 01 00 00 00 00 00 00
; p_align: u64 = 0x1000
00 10 00 00 00 00 00 00
; End of program header
;
; @die: +0
; mov rax, 60 (exit)
b8 3c 00 00 00
; mov rdi, 0 (exit code)
bf 00 00 00 00
; syscall
0f 05
; ret
c3
;
; @fopen:
; mov rax, 2 (sys_open)
b8 02 00 00 00
; mov rsi, 0
be 00 00 00 00
; mov rdx, 0
ba 00 00 00 00
; syscall
0f 05
; ret
c3
;
; fopen_write:
; mov rax, 2 (sys_open)
b8 02 00 00 00
; mov rsi, 0x241 (O_WRONLY | O_CREAT | O_TRUNC)
be 41 02 00 00
; mov rdx, 0o644 (file mode)
ba a4 01 00 00
; syscall
0f 05
; ret
c3
;
; @fstat:
; mov rax, 5 (sys_fstat)
b8 05 00 00 00
; syscall
0f 05
; ret
c3
;
; @fsize:
; push rdi
57
; push rsi
56
; call 'fstat
e8 f1 ff ff ff
; pop rsi
5e
; pop rdi
5f
; mov rax, [rsi+48] (st_size)
48 8b 46 30
; ret
c3
;
; @memcpy:
; test rdx, rdx
48 85 d2
; jz 'memcpy_exit
74 0f
; mov al, byte [rdi]
8a 07
; mov byte [rsi], al
88 06
; inc rsi
48 ff c6
; inc rdi
48 ff c7
; dec rdx
48 ff ca
; jmp 'memcpy
eb ec
; @memcpy_exit:
c3
;
; @_start:
; mov eax, dword [rsp] (argc)
8b 04 24
; lea rbx, [rsp+8] (argv)
48 8d 5c 24 08
; sub rsp, 0x4000
48 81 ec 00 40 00 00
; mov r15, rsp
49 89 e7
; mov dword [r15+0x1010], eax (store argc)
41 89 87 10 10 00 00
; mov qword [r15+0x1018], rbx (store argv)
49 89 9f 18 10 00 00
; cmp eax, 3
83 f8 03
; jl 'die
0f 8c 7b ff ff ff
;
; mov rdi, qword [r15+0x1018] (argv)
49 8b bf 18 10 00 00
; add rdi, 8 (argv[1])
48 83 c7 08
; mov rdi, [rdi] (argv[1])
48 8b 3f
; call 'fopen_write
e8 87 ff ff ff
;
; mov dword [r15+0x101c], eax (store file descriptor)
41 89 87 0c 10 00 00
; mov rdi, qword [r15+0x1018] (argv)
49 8b bf 18 10 00 00
; add rdi, 16 (argv[2])
48 83 c7 10
; mov rdi, [rdi] (argv[2])
48 8b 3f
; call 'fopen
e8 5b ff ff ff
;
; mov dword [r15+0x1008], eax (store file descriptor)
41 89 87 08 10 00 00
; mov edi, eax (fd)
89 c7
; lea rsi, [r15]
49 8d 37
; call 'fsize
e8 76 ff ff ff
;
; mov dword [r15+0x1004], eax (store file size)
41 89 87 04 10 00 00
; mov edi, 0x400000
bf 00 00 40 00
; mov rsi, r15
4c 89 fe
; mov edx, 0x40 (size)
ba 40 00 00 00
; call 'memcpy
e8 6b ff ff ff
;
; mov edi, 0x400040
bf 40 00 40 00
; lea rsi, [r15+0x40]
49 8d 77 40
; mov edx, 0x38 (size)
ba 38 00 00 00
; call 'memcpy
e8 58 ff ff ff
;
; mov eax, 0x78
b8 78 00 00 00
; add eax, dword [r15+0x1004] (file size)
41 03 87 04 10 00 00
; mov dword [r15+0x60], eax (store file end)
41 89 47 60
; mov dword [r15+0x68], eax (store file end)
41 89 47 68
;
; sub rax, 5
48 83 e8 05
; add rax, 0x400000
48 05 00 00 40 00
; mov dword [r15+0x18], eax (store file end - 5)
41 89 47 18
;
; mov edi, dword [r15+0x100c] (fd)
41 8b bf 0c 10 00 00
; mov rsi, r15
4c 89 fe
; mov edx, 0x78 (size)
ba 78 00 00 00
; mov eax, 1 (sys_write)
b8 01 00 00 00
; syscall
0f 05
; @write_loop:
; mov edi, dword [r15+0x1008] (fd)
41 8b bf 08 10 00 00
; mov rsi, r15
4c 89 fe
; mov edx, 0x1000 (size)
ba 00 10 00 00
; mov eax, 0 (sys_read)
b8 00 00 00 00
; syscall
0f 05
; test rax, rax
48 85 c0
; jle 'die
0f 8e ba fe ff ff
;
; mov dword [r15+0x1000], eax (store bytes read)
41 89 87 00 10 00 00
; mov edi, dword [r15+0x100c] (fd)
41 8b bf 0c 10 00 00
; mov rsi, r15
4c 89 fe
; mov edx, dword [r15+0x1000] (bytes read)
41 8b 97 00 10 00 00
; mov eax, 1 (sys_write)
b8 01 00 00 00
; syscall
0f 05
; cmp dword [r15+0x1000], 0x1000
41 81 bf 00 10 00 00 00 10 00 00
; jz 'write_loop
74 b5
; call 'die
e8 89 fe ff ff
; end

22
stages/hex1/helloworld.h1 Normal file
View file

@ -0,0 +1,22 @@
; Code segment
;
; _start
@hello_world:
68 65 6c 6c 6f 20 77 6f 72 6c 64 21
@_start:
; mov rax, 1
48 c7 c0 01 00 00 00
; mov rdi, 1
48 c7 c7 01 00 00 00
; mov rsi, [@hello_world]
48 8d 35 'hello_world
; mov rdx, 12
48 c7 c2 0c 00 00 00
; syscall
0f 05
; mov rax, 60 (sys_exit)
48 c7 c0 3c 00 00 00
; xor rdi, rdi
48 31 ff
; syscall
0f 05

319
stages/hex1/hex1.asm Normal file
View file

@ -0,0 +1,319 @@
;; This is the 2nd stage of the hex compiler.
;; It still requires hex input, but it can automatically calculate rip-relative offsets to labels
;; we need the following variables:
;; +0 = buf: [u8; 0x1000]
;; +0x1000 = write_buf: [u8; 0x4000]
;; +0x5000 = read_cursor: u32
;; +0x5004 write_cursor: u32
;; +0x5008 read_end: u32
;; +0x500c file_offset: u32 (the offset of the current file in the overall input, used for calculating label offsets)
;; +0x5010 labels: [(u64, u32); 340]
;; +0x6000 num_labels: u32
;; +0x6004 scratch space
section .data
_start_lbl db "_start", 0
section .text
;; exits the program
panic:
mov rax, 60 ; syscall: exit
mov rdi, 1 ; status: 1
syscall
;; exits the program
die:
mov rax, 60 ; syscall: exit
mov rdi, 0 ; status: 0
syscall
;; calculates a quick hash of a byte sequence given by rdi (pointer) and rsi (length)
fasthash:
mov eax, 0x811c9dc5 ; FNV offset basis
.fashhash_loop:
test rsi, rsi
jz .fashhash_exit
movzx ecx, byte [rdi] ; get next byte
xor eax, ecx
imul eax, 0x1000193 ; FNV prime
inc rdi
dec rsi
jmp .fashhash_loop
.fashhash_exit:
ret
;; push the label pointed at by rdi with length rsi, and the current file offset onto the labels array.
push_lbl:
mov eax, dword [r15 + 0x6000]
cmp eax, 340
jge panic
lea edx, [rax + rax*2]
shl rdx, 2
push rdx
call fasthash
pop rdx ; rdx = offset of label entry in labels array
lea rdi, [r15 + 0x5010]
mov qword [rdi + rdx], rax ; store hash in labels array
mov eax, dword [r15 + 0x500c] ; get current file offset
mov dword [rdi + rdx + 8], eax ; store file offset in labels array
inc dword [r15 + 0x6000]
ret
;; searches for the offset of the label with the name pointed at by rdi with length rsi, and returns it in rax. If the label is not found, dies.
find_lbl:
call fasthash
lea rdx, [r15 + 0x5010] ; rdx = pointer to start of labels array
mov ebx, dword [r15 + 0x6000]
.find_lbl_loop:
test rbx, rbx
jz .find_lbl_not_found
dec rbx
cmp qword [rdx], rax
je .find_lbl_found
add rdx, 12 ; move to next label entry
jmp .find_lbl_loop
.find_lbl_not_found:
call panic
.find_lbl_found:
mov eax, dword [rdx + 8] ; get file offset of label
ret
;; write up to write_cursor to stdout, and then reset the write cursor to 0
wrt_stdout:
mov rax, 1 ; syscall: write
mov rdi, 1 ; fd: stdout
lea rsi, [r15 + 0x1000] ; buf: pointer to write_buf
mov edx, dword [r15 + 0x5004] ; count: write_cursor
syscall
mov dword [r15 + 0x5004], 0
ret
die_jmp_start:
;; @_start_lbl: 5F 73 74 61 72 74 00 00
;; lea rdi, [@_start_lbl]
mov rdi, _start_lbl
mov rsi, 6
call find_lbl
add dword [r15 + 0x500c], 5 ; advance the file offset by 5
sub eax, dword [r15 + 0x500c] ; add current file offset
mov edi, dword [r15 + 0x5004] ; get current write cursor
lea rdi, [r15 + rdi + 0x1000] ; rdi = pointer to write location in write_buf
mov byte [rdi], 0xe9 ; write the opcode for jmp rel32
mov dword [rdi + 1], eax ; write the offset of the label reference
add dword [r15 + 0x5004], 5 ; advance the write cursor by 5
call wrt_stdout
call die
;; reads up to 0x1000 bytes from stdin into buf, and sets read_end to the number of bytes read, and resets read_cursor to 0
;; dies if no bytes were read (if it returns, there were bytes read)
rd_stdin:
mov rax, 0 ; syscall: read
mov rdi, 0 ; fd: stdin
mov rsi, r15
mov rdx, 0x1000 ; count: size of buffer
syscall
test eax, eax
jle die_jmp_start
mov dword [r15 + 0x5008], eax ; store number of bytes read in read_end
mov dword [r15 + 0x5000], 0 ; reset read_cursor to 0
ret
;; return the next byte from the input
;; automatically writes to stdout and reads from stdin if we are at the end of the buffer
rd_buf:
mov eax, dword [r15 + 0x5000] ; get read_cursor
cmp eax, dword [r15 + 0x5008] ; compare to read_end
jl .rd_buf_end
call wrt_stdout
call rd_stdin
jmp .rd_buf_end
.rd_buf_end:
mov edx, dword [r15 + 0x5000] ; get read_cursor
xor rax, rax
mov al, byte [r15 + rdx] ; get next byte from buf
inc dword [r15 + 0x5000] ; advance read_cursor
ret
;; The grammar is slightly updated from hex0, as follows:
;; program = (label-def / instruction-seq / comment)*
;; comment = ";" [^\n]* "\n"
;; label-def = "@" label-name ":" comment?
;; instruction-seq = instruction+ comment?
;; instruction = label-ref / opcode
;; label-ref = "'" label-name
;; opcode = hex hex
;; label-name = [a-zA-Z0-9_-]+
;; hex = [0-9a-f][0-9a-f]
;;
;; whitespaces may occur between any tokens and are ignored.
;; whitespace = [ \t\n]+
skip_line:
cmp al, `\n`
jne .skip_line_read
call rd_buf
ret
.skip_line_read:
call rd_buf
jmp skip_line
skip_whitespaces:
cmp al, ' '
je .skip_whitespace_read
cmp al, `\t`
je .skip_whitespace_read
cmp al, `\n`
je .skip_whitespace_read
ret
.skip_whitespace_read:
call rd_buf
jmp skip_whitespaces
skip_comment:
cmp al, ";"
jne .skip_comment_ret
call skip_line
ret
.skip_comment_ret:
ret
get_next_token:
cmp al, ";"
je .get_next_token_skip
cmp al, ' '
je .get_next_token_skip
cmp al, `\t`
je .get_next_token_skip
cmp al, `\n`
je .get_next_token_skip
ret
.get_next_token_skip:
call skip_whitespaces
call skip_comment
jmp get_next_token
char_to_u4:
cmp al, '0'
jb .invalid_hex
cmp al, '9'
jbe .is_digit
cmp al, 'a'
jb .invalid_hex
cmp al, 'f'
jbe .is_lower_hex
cmp al, 'A'
jb .invalid_hex
cmp al, 'F'
jbe .is_upper_hex
jmp .invalid_hex
.invalid_hex:
call panic
.is_digit:
sub al, '0'
ret
.is_lower_hex:
sub al, 'a' - 10
ret
.is_upper_hex:
sub al, 'A' - 10
ret
octet:
sub rsp, 8
call char_to_u4
mov byte [rsp], al
call rd_buf
call char_to_u4
mov bl, byte [rsp]
shl bl, 4
or al, bl
mov edi, dword [r15 + 0x5004] ; get current write cursor
lea rdx, [r15 + 0x1000]
mov byte [rdi + rdx], al ; write the byte to the write_buf
inc dword [r15 + 0x5004] ; advance the write cursor by 1
inc dword [r15 + 0x500c] ; advance file offset by 1
add rsp, 8
ret
label_ref:
cmp al, "'"
jne .not_label_ref
xor rsi, rsi ; rsi = length of label
.label_ref_loop:
push rsi
call rd_buf
pop rsi
lea rdi, [r15 + 0x6004] ; rdi = pointer to scratch
cmp al, ' '
je .label_ref_ret
cmp al, `\t`
je .label_ref_ret
cmp al, `\n`
je .label_ref_ret
mov byte [rdi + rsi], al
inc rsi
jmp .label_ref_loop
.label_ref_ret:
lea rdi, [r15 + 0x6004]
call find_lbl
add dword [r15 + 0x500c], 4 ; advance the file offset by 4
sub eax, dword [r15 + 0x500c] ; add current file offset
mov edi, dword [r15 + 0x5004] ; get current write cursor
mov dword [r15 + 0x1000 + rdi], eax ; write the opcode for jmp rel32
add dword [r15 + 0x5004], 4 ; advance the write cursor by 4
ret
.not_label_ref:
call octet
ret
;; read the label into scratch, call push_lbl
label:
cmp al, '@'
jne .not_label
xor rsi, rsi ; rsi = length of label
.label_loop:
push rsi
call rd_buf
pop rsi
lea rdi, [r15 + 0x6004] ; rdi = pointer to scratch
cmp al, ':'
je .label_ret
mov byte [rdi + rsi], al
inc rsi
jmp .label_loop
.label_ret:
lea rdi, [r15 + 0x6004] ; rdi = pointer to scratch
call push_lbl
ret
.not_label:
call label_ref
ret
global _start
_start:
push rbp
mov rbp, rsp
sub rsp, 0x8000 ; allocate space for our variables + some scratch space
mov r15, rsp ; r15 will point to the start of our variables
mov dword [r15 + 0x6000], 0 ; num_labels = 0
mov dword [r15 + 0x500c], 0 ; file_offset = 0
mov dword [r15 + 0x5008], 0 ; read_end = 0
mov dword [r15 + 0x5004], 0 ; write_cursor = 0
mov dword [r15 + 0x5000], 0 ; read_cursor = 0
.loop:
call rd_buf
call get_next_token
call label
jmp .loop

468
stages/hex1/hex1.in Normal file
View file

@ -0,0 +1,468 @@
; @panic: +0
; mov rax, 60 (exit)
b8 3c 00 00 00
; mov rdi, 1 (exit code)
bf 01 00 00 00
; syscall
0f 05
;
; @die: +12
; mov rax, 60 (exit)
b8 3c 00 00 00
; mov rdi, 0 (exit
bf 00 00 00 00
; syscall
0f 05
;
; @fasthash_exit: +24
; ret
c3
; @fasthash: +25
; mov rax, 0x811c9dc5 (FNV offset basis)
b8 c5 9d 1c 81
; @fasthash_loop: +30
; test rsi, rsi
48 85 f6
; jz 'fasthash_exit (+39)
0f 84 f1 ff ff ff
; movzx ecx, byte [rdi]
0f b6 0f
; xor eax, ecx
31 c8
; imul eax, eax, 0x1000193 (FNV prime)
69 c0 93 01 00 01
; inc rdi
48 ff c7
; dec rsi
48 ff ce
; jmp 'fasthash_loop (+61)
e9 e1 ff ff ff
;
; @push_lbl: +61
; mov eax, [r15+0x6000]
41 8b 87 00 60 00 00
; cmp eax, 0x154
3d 54 01 00 00
; jge 'panic (+79)
0f 8d b1 ff ff ff
; lea edx, [rax + rax*2]
8d 14 40
; shl rdx, 2
48 c1 e2 02
; push rdx
52
; call 'fasthash (+92)
e8 bd ff ff ff
; pop rdx
5a
; lea rdi, [r15+0x5010]
49 8d bf 10 50 00 00
; mov qword [rdi+rdx], rax
48 89 04 17
; mov eax, dword [r15+0x500c]
41 8b 87 0c 50 00 00
; mov dword [rdi+rdx+8], eax
89 44 17 08
; inc dword [r15+0x6000]
41 ff 87 00 60 00 00
; ret
c3
;
; @find_lbl_not_found: (+123)
; call 'panic (+128)
e8 80 ff ff ff
; @find_lbl_found: (+128)
; mov eax, dword [rdx+8]
8b 42 08
; ret
c3
; @find_lbl: +132
; call 'fasthash (+137)
e8 90 ff ff ff
; lea rdx, [r15+0x5010]
49 8d 97 10 50 00 00
; mov edx, dword [r15+0x6000]
41 8b 9f 00 60 00 00
; @find_lbl_loop: (+151)
; test rbx, rbx
48 85 db
; jz 'find_lbl_not_found (+160)
0f 84 db ff ff ff
; dec rbx
48 ff cb
; cmp [rdx], rax
48 39 02
; jz 'find_lbl_found (+172)
0f 84 d4 ff ff ff
; add rdx, 0xc
48 83 c2 0c
; jmp 'find_lbl_loop (+181)
e9 e2 ff ff ff
;
; @wrt_stdout: (+181)
; mov rax, 1 (sys_write)
b8 01 00 00 00
; mov rdi, 1 (stdout)
bf 01 00 00 00
; lea rsi, [r15+0x1000]
49 8d b7 00 10 00 00
; mov edx, dword [r15+0x5004]
41 8b 97 04 50 00 00
; syscall
0f 05
; mov dword [r15+0x5004], 0
41 c7 87 04 50 00 00 00 00 00 00
; ret
c3
;
; @_start_lbl: (+219)
5f 73 74 61 72 74 00
; @die_jmp_start: (+226)
; lea rdi, [rip+@_start_lbl] (+233)
48 8d 3d f2 ff ff ff
; mov esi, 6
be 06 00 00 00
; call 'find_lbl (+243)
e8 91 ff ff ff
; add dword [r15+0x500c], 5
41 83 87 0c 50 00 00 05
; sub eax, dword [r15+0x500c]
41 2b 87 0c 50 00 00
; mov edi, dword [r15+0x5004]
41 8b bf 04 50 00 00
; lea rdi, [r15+rdi*1+0x1000]
49 8d bc 3f 00 10 00 00
; mov byte [rdi], 0xe9
c6 07 e9
; mov dword [rdi+1], eax
89 47 01
; add dword [r15+0x5004], 5
41 83 87 04 50 00 00 05
; call 'wrt_stdout (+292)
e8 91 ff ff ff
; call 'die (+297)
e8 e3 fe ff ff
;
; @rd_stdin: (+297)
; mov rax, 0 (sys_read)
b8 00 00 00 00
; mov rdi, 0 (stdin)
bf 00 00 00 00
; mov rsi, r15
4c 89 fe
; mov rdx, 0x1000
ba 00 10 00 00
; syscall
0f 05
; test rax, rax
85 c0
; jle 'die_jmp_start (+325)
0f 8e 9d ff ff ff
; mov dword [r15+0x5008], eax
41 89 87 08 50 00 00
; mov dword [r15+0x5000], 0
41 c7 87 00 50 00 00 00 00 00 00
; ret
c3
;
; @rd_buf_end: (+344)
; mov edx, dword [r15+0x5000]
41 8b 97 00 50 00 00
; xor rax, rax
48 31 c0
; mov al, byte [rdi+rdx]
41 8a 04 17
; inc dword [r15+0x5000]
41 ff 87 00 50 00 00
; ret
c3
; @rd_buf: (+366)
; mov eax, dword [r15+0x5000]
41 8b 87 00 50 00 00
; cmp eax, dword [r15+0x5008]
41 3b 87 08 50 00 00
; jl 'rd_buf_end (+386)
0f 8c d6 ff ff ff
; call 'wrt_stdout (+391)
e8 2e ff ff ff
; call 'rd_stdin (+396)
e8 9d ff ff ff
; jmp 'rd_buf_end (+401)
e9 c7 ff ff ff
;
;
; @skip_line_read: (+401)
; call 'rd_buf (+406)
e8 d8 ff ff ff
; @skip_line: (+406)
; cmp al, 0xa (newline)
3c 0a
; jnz 'skip_line_read (+414)
0f 85 f3 ff ff ff
; call 'rd_buf (+419)
e8 cb ff ff ff
; ret
c3
;
; @skip_whitespace_read: (+420)
; call 'rd_buf (+425)
e8 c5 ff ff ff
; @skip_whitespace: (+425)
; cmp al, 0x20 (space)
3c 20
; jz 'skip_whitespace_read (+433)
0f 84 f3 ff ff ff
; cmp al, 0xa (newline)
3c 0a
; jz 'skip_whitespace_read (+441)
0f 84 eb ff ff ff
; cmp al, 0x9 (tab)
3c 09
; jz 'skip_whitespace_read (+449)
0f 84 e3 ff ff ff
; ret
c3
;
; @skip_comment_ret: (+450)
; ret
c3
; @skip_comment: (+451)
; cmp al, 0x3b (semicolon)
3c 3b
; jnz 'skip_comment_ret (+459)
0f 85 f7 ff ff ff
; call 'skip_line (+464)
e8 c6 ff ff ff
; ret
c3
;
; @get_next_token_skip: (+465)
; call 'skip_whitespace (+470)
e8 d3 ff ff ff
; call 'skip_comment (+475)
e8 e8 ff ff ff
; @get_next_token: (+475)
; cmp al, 0x3b (semicolon)
3c 3b
; jz 'get_next_token_skip (+483)
0f 84 ee ff ff ff
; cmp al, 0xa (newline)
3c 0a
; jz 'get_next_token_skip (+491)
0f 84 e6 ff ff ff
; cmp al, 0x20 (space)
3c 20
; jz 'get_next_token_skip (+499)
0f 84 de ff ff ff
; cmp al, 0x9 (tab)
3c 09
; jz 'get_next_token_skip (+507)
0f 84 d6 ff ff ff
; ret
c3
;
; @invalid_hex: (+508)
; call 'panic (+513)
e8 ff fd ff ff
; @is_digit: (+513)
; sub al, 0x30 (digit '0')
2c 30
; ret
c3
; @is_low_alpha: (+516)
; sub al, 0x57 (letter 'a' - 10)
2c 57
; ret
c3
; @is_up_alpha: (+519)
; sub al, 0x37 (letter 'A' - 10)
2c 37
; ret
c3
; @char_to_u4: (+522)
; cmp al, 0x30 (digit '0')
3c 30
; jb 'invalid_hex (+530)
0f 82 ea ff ff ff
; cmp al, 0x39 (digit '9')
3c 39
; jbe 'is_digit (+538)
0f 86 e7 ff ff ff
; cmp al, 0x41 (letter 'A')
3c 41
; jb 'invalid_hex (+546)
0f 82 da ff ff ff
; cmp al, 0x46 (letter 'F')
3c 46
; jbe 'is_up_alpha (+554)
0f 86 dd ff ff ff
; cmp al, 0x61 (letter 'a')
3c 61
; jb 'invalid_hex (+562)
0f 82 ca ff ff ff
; cmp al, 0x66 (letter 'f')
3c 66
; jbe 'is_low_alpha (+570)
0f 86 ca ff ff ff
; jmp 'invalid_hex (+575)
e9 bd ff ff ff
;
; @octet: (+575)
; sub rsp, 8
48 83 ec 08
; call 'char_to_u4 (+584)
e8 ca ff ff ff
; mov byte [rsp], al
88 04 24
; call 'rd_buf (+592)
e8 1e ff ff ff
; call 'char_to_u4 (+597)
e8 b5 ff ff ff
; mov bl, byte [rsp]
8a 1c 24
; shl bl, 4
c0 e3 04
; or al, bl
08 d8
; mov edi, dword [r15+0x5004]
41 8b bf 04 50 00 00
; lea rdx, [r15+0x1000]
49 8d 97 00 10 00 00
; mov byte [rdi+rdx], al
88 04 17
; inc dword [r15+0x5004]
41 ff 87 04 50 00 00
; inc dword [r15+0x500c]
41 ff 87 0c 50 00 00
; add rsp, 8
48 83 c4 08
; ret
c3
;
; @label_ref_ret: (+641)
; lea rdi, [r15+0x6004]
49 8d bf 04 60 00 00
; call 'find_lbl (+653)
e8 f7 fd ff ff
; add dword [r15+0x500c], 4
41 83 87 0c 50 00 00 04
; sub eax, dword [r15+0x500c]
41 2b 87 0c 50 00 00
; mov edi, dword [r15+0x5004]
41 8b bf 04 50 00 00
; mov dword [r15+rdi*1+0x1000], eax
41 89 84 3f 00 10 00 00
; add dword [r15+0x5004], 4
41 83 87 04 50 00 00 04
; ret
c3
; @not_label_ref: (+692)
; call 'octet (+697)
e8 86 ff ff ff
; ret
c3
; @label_ref: (+698)
; cmp al, 0x27 (single quote)
3c 27
; jnz 'not_label_ref (+706)
0f 85 f2 ff ff ff
; xor rsi, rsi
48 31 f6
; @label_ref_loop: (+709)
; push rsi
56
; call 'rd_buf (+715)
e8 a3 fe ff ff
; pop rsi
5e
; lea rdi, [r15+0x6004]
49 8d bf 04 60 00 00
; cmp al, 0x20 (space)
3c 20
; jz 'label_ref_ret (+731)
0f 84 a6 ff ff ff
; cmp al, 0x9 (tab)
3c 09
; jz 'label_ref_ret (+739)
0f 84 9e ff ff ff
; cmp al, 0xa (newline)
3c 0a
; jz 'label_ref_ret (+747)
0f 84 96 ff ff ff
; mov byte [rdi+rsi*1], al
88 04 37
; inc rsi
48 ff c6
; jmp 'label_ref_loop (+758)
e9 cf ff ff ff
;
; @label_ret: (+758)
; lea rdi, [r15+0x6004]
49 8d bf 04 60 00 00
; call 'push_lbl (+770)
e8 3b fd ff ff
; ret
c3
; @not_label: (+771)
; call 'label_ref (+776)
e8 b2 ff ff ff
; ret
c3
; @label: (+777)
; cmp al, 0x40 (at sign)
3c 40
; jnz 'not_label (+785)
0f 85 f2 ff ff ff
; xor rsi, rsi
48 31 f6
; @label_loop: (+788)
; push rsi
56
; call 'rd_buf (+794)
e8 54 fe ff ff
; pop rsi
5e
; lea rdi, [r15+0x6004]
49 8d bf 04 60 00 00
; cmp al, 0x3a (colon)
3c 3a
; jz 'label_ret (+810)
0f 84 cc ff ff ff
; mov byte [rdi+rsi*1], al
88 04 37
; inc rsi
48 ff c6
; jmp 'label_loop (+821)
e9 df ff ff ff
;
; @_start: (+821)
; push rbp
55
; mov rbp, rsp
48 89 e5
; sub rsp, 0x8000
48 81 ec 00 80 00 00
; mov r15, rsp
49 89 e7
; mov dword [r15+0x6000], 0
41 c7 87 00 60 00 00 00 00 00 00
; mov dword [r15+0x500c], 0
41 c7 87 0c 50 00 00 00 00 00 00
; mov dword [r15+0x5008], 0
41 c7 87 08 50 00 00 00 00 00 00
; mov dword [r15+0x5004], 0
41 c7 87 04 50 00 00 00 00 00 00
; mov dword [r15+0x5000], 0
41 c7 87 00 50 00 00 00 00 00 00
; @main_loop:
; call 'rd_buf (+895)
e8 ef fd ff ff
; call 'get_next_token (+900)
e8 57 fe ff ff
; call 'label (+905)
e8 80 ff ff ff
; jmp 'main_loop (+910)
e9 ec ff ff ff
; jmp 'main
e9 a2 ff ff ff
; end

23
stages/lisp0/Makefile Normal file
View file

@ -0,0 +1,23 @@
# test.bin: lisp.rs lisp.o
# rustc -Clink-arg=-fuse-ld=mold -Clink-arg=lisp.o --edition=2024 --test -g $< -o $@
# lisp.o: lisp.asm
# nasm -g -f elf64 -o lisp.o lisp.asm
# test: test.bin
# ./test.bin
test.bin: test.rs lisp1.o
rustc -Clink-arg=-fuse-ld=mold -Clink-arg=lisp1.o --edition=2024 --test -g $< -o $@
lisp1.o: lisp1.asm
nasm -g -f elf64 -o lisp1.o lisp1.asm
test: test.bin
./test.bin --nocapture --test-threads=1
lisp1: lisp1.o
ld -m elf_x86_64 -e _interp_entry -o lisp1 lisp1.o
clean:
rm -f lisp.o test.bin lisp1.o test1.bin lisp1

2115
stages/lisp0/lisp.asm Normal file

File diff suppressed because it is too large Load diff

56
stages/lisp0/lisp.in Normal file
View file

@ -0,0 +1,56 @@
;; Atom String Table
; t
116
; quote
113 117 111 116 101
; +
43
; -
45
; *
42
; /
47
; %
37
; <
60
; =
61
; car
99 97 114
; cdr
99 100 114
; not
100 111 116
; lambda
108 97 109 98 100 97
;define
100 101 102 105 100 101
; eval
101 118 97 108
; if
105 102
; let
108 101 116
; let*
108 101 116 42
; str-len
115 116 114 45 108 101 100
; list-len
108 105 115 116 45 108 101 100
; str-parts
115 116 114 45 112 97 114 116 115
; syscall
115 121 115 99 97 108 108
; print-env
112 114 105 100 116 45 101 100 118
; \Space
92 83 112 97 99 101
; \NL
92 78 76
; \Tab
92 84 97 98
;; Atom Table
; ref-count| length | pointer to string |
01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

190
stages/lisp0/lisp.rs Normal file
View file

@ -0,0 +1,190 @@
unsafe extern "C" {
fn init_atoms();
fn parse_next_token(src: *mut Source<'_>) -> Object;
fn init_env() -> Object;
fn eval(expr: Object, env: Object) -> Object;
fn get_env() -> Object;
#[link_name = "nil"]
static NIL: ();
#[link_name = "env"]
static mut GENV: ();
}
struct Source<'a> {
iter: core::iter::Peekable<core::slice::Iter<'a, u8>>,
}
impl<'a> From<&'a [u8]> for Source<'a> {
fn from(value: &'a [u8]) -> Self {
Self {
iter: value.iter().peekable(),
}
}
}
#[unsafe(no_mangle)]
extern "C" fn peekc(src: *mut Source) -> u8 {
unsafe { (*src).iter.peek().cloned().unwrap_or(&0).clone() }
}
#[unsafe(no_mangle)]
extern "C" fn getc(src: *mut Source) -> u8 {
unsafe { (*src).iter.next().unwrap_or(&0).clone() }
}
#[unsafe(no_mangle)]
extern "C" fn alloc(size: usize, align: usize) -> *mut u8 {
let layout = core::alloc::Layout::from_size_align(size, align).unwrap();
unsafe { std::alloc::alloc(layout) }
}
#[unsafe(no_mangle)]
extern "C" fn dealloc(ptr: *mut u8, size: usize, align: usize) {
let layout = core::alloc::Layout::from_size_align(size, align).unwrap();
unsafe { std::alloc::dealloc(ptr, layout) }
}
#[unsafe(no_mangle)]
extern "C" fn panic_abort(cstr: *const u8) -> ! {
if cstr.is_null() {
panic!("abort");
} else {
let mut len = 0;
while unsafe { *cstr.add(len) } != 0 {
len += 1;
}
let slice = unsafe { core::slice::from_raw_parts(cstr, len) };
let message = core::str::from_utf8(slice).unwrap_or("abort");
panic!("{}", message);
}
}
#[unsafe(no_mangle)]
extern "C" fn print(obj: Object) {
println!("{:?}", obj);
}
static mut ENV_INIT: std::cell::LazyCell<Object> =
std::cell::LazyCell::new(|| unsafe { init_env() });
#[repr(C)]
#[derive(Copy, Clone)]
struct Object(*mut ());
// impl Clone for Object {
// fn clone(&self) -> Self {
// unsafe {
// *self.ptr().cast::<u32>().as_mut().unwrap() += 1;
// }
// Self(self.0)
// }
// }
// impl Drop for Object {
// fn drop(&mut self) {
// unsafe {
// *self.ptr().cast::<u32>().as_mut().unwrap() -= 1;
// }
// }
// }
use std::fmt;
impl fmt::Debug for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 as *const () == &raw const NIL {
return write!(f, "nil");
}
match self.tag() {
0 => write!(f, "'{}'", (self.0.addr() >> 8) as u8 as char),
1 => unsafe {
let car = self.ptr().byte_add(8).cast::<Object>().read();
let cdr = self.ptr().byte_add(16).cast::<Object>().read();
write!(f, "({car:?} . {cdr:?})")
},
2 => unsafe {
let car = self.ptr().byte_add(8).cast::<Object>().read();
let env = self.ptr().byte_add(16).cast::<Object>().read();
assert_eq!(
car.tag(),
1,
"Expected a pair for lambda cdr, got tag {}",
car.tag()
);
let params = car.ptr().byte_add(8).cast::<Object>().read();
let body = car.ptr().byte_add(16).cast::<Object>().read();
write!(f, "λ ")?;
if env.0 as *const () == &raw const GENV || env.0 as *const () == &raw const NIL {
write!(f, " #<g_env> ++ ")?;
} else {
write!(f, " {env:?} ++ ")?;
}
write!(f, "{params:?} {body:?})")
},
3 => unsafe {
let str_ptr = self.ptr().byte_add(8).cast::<*const u8>().read();
let len = self.ptr().byte_add(4).cast::<u32>().read() as usize;
let slice = core::slice::from_raw_parts(str_ptr, len);
let string = core::str::from_utf8(slice).unwrap_or("<invalid utf-8>");
write!(f, "'{string}")
},
6 => unsafe {
let str_ptr = self.ptr().byte_add(8).cast::<*const u8>().read();
let len = self.ptr().byte_add(4).cast::<u32>().read() as usize;
let slice = core::slice::from_raw_parts(str_ptr, len);
let string = core::str::from_utf8(slice).unwrap_or("<invalid utf-8>");
write!(f, "\"{string}\"")
},
4 => unsafe {
let num = self.ptr().byte_add(8).cast::<u64>().read();
write!(f, "{num}")
},
5 => unsafe { write!(f, "#<prim>") },
_ => write!(f, "#<unknown tag {}>", self.tag()),
}
}
}
impl Object {
fn tag(&self) -> u8 {
self.0.addr() as u8 & 0b111
}
fn ptr(&self) -> *mut () {
self.0.map_addr(|addr| addr & !0b111)
}
}
#[test]
fn test_parse_list() {
let env = unsafe { *ENV_INIT };
println!("env: {:?}\n", env);
// let input = b"(let ((x 10) (y 20)) (+ x y))";
let input = b"(if () 1 2)";
let hello_world = b"(let* (
(msg \"hello world\\n\")
(msg-parts (str-parts msg))
(ptr (car msg-parts))
(len (cdr msg-parts))
(fd 1))
(syscall 1 fd ptr len))";
// let math = br#"
// (let (
// (a (< 1 2))
// (b (< 2 1))
// (c (+ 1 2 3))
// (d (* 2 3 4))
// (e (/ 10 2))
// (f (% 10 3))
// )
// (print-env)
// )
// "#;
let mut src = Source::from(&hello_world[..]);
let sexp = unsafe { parse_next_token(&raw mut src) };
println!("> {:?}", sexp);
let result = unsafe { eval(sexp, get_env()) };
println!("{:?}\n", result);
println!("env: {:?}", unsafe { get_env() });
}

3554
stages/lisp0/lisp1.asm Normal file

File diff suppressed because it is too large Load diff

568
stages/lisp0/test.rs Normal file
View file

@ -0,0 +1,568 @@
#![feature(allocator_api, box_as_ptr)]
unsafe extern "C" {
fn heap_alloc(size: usize, align: usize) -> *mut u8;
fn heap_dealloc(ptr: *mut u8, size: usize, align: usize);
fn init_source(source: *mut Source, fd: i32) -> *mut Source;
fn getc() -> SourceIterResult;
fn peekc() -> SourceIterResult;
fn eval(expr: Object, env: Object) -> Object;
fn init_env() -> Object;
fn parse_next_token() -> Object;
#[link_name = "ifile"]
static mut IFILE: Source;
#[link_name = "nil"]
static NIL: ();
#[link_name = "env"]
static GENV: ();
}
use std::ptr::NonNull;
#[repr(C)]
#[derive(Copy, Clone)]
struct Object(*mut ());
impl Object {
const BYTE: u8 = 0;
const NUM: u8 = 1;
const PRIM: u8 = 2;
const CONS: u8 = 3;
const CLOS: u8 = 4;
const ATOM: u8 = 5;
const ARR: u8 = 6;
const NUM_MAGIC: u32 = 0x5555;
fn is_nil(&self) -> bool {
self.0 as *const () == &raw const NIL
}
fn tag(&self) -> u8 {
self.0.addr() as u8 & 0b111
}
fn bits(&self) -> usize {
self.0.addr()
}
fn ptr(&self) -> *mut () {
self.0.map_addr(|addr| addr & !0b111)
}
fn is_g_env(&self) -> bool {
self.0 as *const () == unsafe { *ENV_INIT }.0 as *const ()
}
}
#[derive(Copy, Clone)]
struct DebugObjectExplicitGenv(Object);
impl std::fmt::Debug for DebugObjectExplicitGenv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[repr(C)]
struct Cons {
refc: u64,
car: Object,
cdr: Object,
}
if self.0.is_g_env() {
let mut proxy = unsafe {
Cons {
refc: 1,
car: self.0.ptr().byte_add(8).cast::<Object>().read(),
cdr: self.0.ptr().byte_add(16).cast::<Object>().read(),
}
};
let obj =
Object((&raw const proxy as *mut ()).map_addr(|addr| addr | Object::CONS as usize));
write!(f, "{:?}", obj)
} else {
return write!(f, "{:?}", self.0);
}
}
}
impl std::fmt::Debug for Object {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_nil() {
return write!(f, "nil");
}
if self.is_g_env() {
return write!(f, "#<g_env>");
}
fn fmt_byte(byte: *const (), f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let c = unsafe { byte.cast::<u8>().read() };
if c.is_ascii_graphic() || c == b' ' {
write!(f, "'{}'", c as char)
} else {
write!(f, "'\\x{:02x}'", c)
}
}
fn fmt_num(num: *const (), f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let num_ptr = unsafe { num.cast::<i64>().read() };
write!(f, "{}", num_ptr)
}
fn fmt_prim(prim: *const (), f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let prim_ptr = unsafe { prim.cast::<*const ()>().read() };
write!(f, "#<prim {:?}>", prim_ptr)
}
fn fmt_cons(cons: *const (), f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (car, cdr) = unsafe { cons.cast::<(Object, Object)>().read() };
if !cdr.is_nil() && cdr.tag() == Object::CONS {
// If the cdr is a cons cell, we can print it as a list
write!(f, "({car:?}")?;
let mut cdr = cdr;
while !cdr.is_nil() && cdr.tag() == Object::CONS {
let (next_car, next_cdr) =
unsafe { cdr.ptr().byte_add(8).cast::<(Object, Object)>().read() };
write!(f, " {next_car:?}")?;
cdr = next_cdr;
}
if cdr.is_nil() {
write!(f, ")")
} else {
write!(f, " {cdr:?})")
}
} else {
write!(f, "({car:?} . {cdr:?})")
}
}
fn fmt_clos(clos: *const (), f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (car, env) = unsafe { clos.cast::<(Object, Object)>().read() };
assert_eq!(
car.tag(),
Object::CONS,
"Expected a pair for lambda cdr, got tag {}",
car.tag()
);
let (params, body) = unsafe { car.ptr().cast::<(Object, Object)>().read() };
write!(f, "λ ")?;
if env.0 as *const () == &raw const NIL {
write!(f, " #<g_env> ++ ")
} else {
write!(f, " {env:?} ++ ")
}
}
fn fmt_atom(atom: *const (), f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (ptr, len) = unsafe {
(
atom.cast::<*const u8>().read(),
atom.byte_add(8).cast::<usize>().read(),
)
};
let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
let string = core::str::from_utf8(slice).unwrap_or("<invalid utf-8>");
write!(f, "'{string}")
}
fn fmt_arr(arr: *const (), f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = unsafe { arr.cast::<u32>().read() } as usize;
let cap = unsafe { arr.byte_add(4).cast::<u32>().read() } as usize;
let data = unsafe { arr.byte_add(8).cast::<Object>().read() };
let data_ptr = data.ptr();
let data_tag = data.tag();
match data_tag {
Object::BYTE => {
let slice = unsafe { core::slice::from_raw_parts(data_ptr as *const u8, len) };
let string = core::str::from_utf8(slice).unwrap_or("<invalid utf-8>");
write!(f, "\"{string}\"")
}
_ => {
const SIZES: [usize; 7] = [1, 8, 8, 16, 16, 16, 16];
let _fn = match data_tag {
Object::BYTE => fmt_byte,
Object::NUM => fmt_num,
Object::PRIM => fmt_prim,
Object::CONS => fmt_cons,
Object::CLOS => fmt_clos,
Object::ATOM => fmt_atom,
Object::ARR => fmt_arr,
_ => unreachable!(),
};
write!(f, "[")?;
for i in 0..len {
let elem = unsafe { data_ptr.byte_add(i * SIZES[data_tag as usize]) };
_fn(elem, f)?;
if i + 1 < len {
write!(f, ", ")?;
}
}
write!(f, "]")
}
}
}
unsafe {
match self.tag() {
Object::BYTE => {
let byte = self.ptr().addr() >> 8 as u8;
fmt_byte(&raw const byte as _, f)
}
Object::NUM => {
let MASK: usize = 0x5555_0000_0000_0000 | Object::NUM as usize;
if self.bits() & MASK == MASK {
let num = (self.bits() >> 8) as i32 as i64;
fmt_num(&raw const num as _, f)
} else {
fmt_num(self.ptr().byte_add(8), f)
}
}
Object::PRIM => fmt_prim(self.ptr().byte_add(8), f),
Object::CONS => fmt_cons(self.ptr().byte_add(8), f),
Object::CLOS => fmt_clos(self.ptr().byte_add(8), f),
Object::ATOM => fmt_atom(self.ptr().byte_add(8), f),
Object::ARR => fmt_arr(self.ptr().byte_add(8), f),
_ => write!(f, "#<unknown tag {}>", self.tag()),
}
}
}
}
#[repr(C)]
struct Source {
fd: i32,
peeked: (u8, u8),
buf: NonNull<u8>,
buf_cur: usize,
buf_end: usize,
}
#[repr(align(8))]
struct Bool(bool);
impl core::ops::Deref for Bool {
type Target = bool;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[repr(C)]
struct SourceIterResult {
c: u8,
some: Bool,
}
struct HeapAlloc;
unsafe impl std::alloc::Allocator for HeapAlloc {
fn allocate(
&self,
layout: std::alloc::Layout,
) -> Result<std::ptr::NonNull<[u8]>, std::alloc::AllocError> {
let ptr = unsafe { heap_alloc(layout.size(), layout.align()) };
if ptr.is_null() {
Err(std::alloc::AllocError)
} else {
Ok(std::ptr::NonNull::slice_from_raw_parts(
std::ptr::NonNull::new(ptr).unwrap(),
layout.size(),
))
}
}
unsafe fn deallocate(&self, ptr: std::ptr::NonNull<u8>, layout: std::alloc::Layout) {
heap_dealloc(ptr.as_ptr(), layout.size(), layout.align());
}
}
static mut ENV_INIT: std::cell::LazyCell<Object> =
std::cell::LazyCell::new(|| unsafe { init_env() });
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::mem::ManuallyDrop;
use std::os::fd::AsRawFd;
#[test]
fn test_heap_alloc_dealloc() {
let small = Box::new_in(42u8, HeapAlloc);
let ptr_sized = Box::new_in(42usize, HeapAlloc);
let big = Box::new_in([0u8; 48], HeapAlloc);
let large = Box::new_in([0u8; 1024], HeapAlloc);
let page_sized = Box::new_in([0u8; 4096], HeapAlloc);
assert_eq!(*small, 42);
assert_ne!(
Box::as_ptr(&small) as *const (),
Box::as_ptr(&ptr_sized) as *const ()
);
let small_ptr = Box::as_ptr(&small) as *const ();
drop(small);
let small2 = Box::new_in(43u8, HeapAlloc);
assert_eq!(*small2, 43);
assert_eq!(small_ptr, Box::as_ptr(&small2) as *const ());
}
#[test]
fn source() {
let mut file = std::fs::File::open("lisp1.asm").unwrap();
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let SourceIterResult { c, some } = getc();
assert!(*some);
assert_eq!(c, b'd');
let SourceIterResult { c, some } = peekc();
assert!(*some);
assert_eq!(c, b'e');
let SourceIterResult { c, some } = getc();
assert!(*some);
assert_eq!(c, b'e');
}
}
#[test]
fn parse_list() {
let file = ManuallyDrop::new(File::open("tests/list.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let obj = parse_next_token();
println!("{:?}", obj);
}
}
#[test]
fn parse_quoted_list() {
let file = ManuallyDrop::new(File::open("tests/quoted-list.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let obj = parse_next_token();
println!("{:?}", obj);
}
}
#[test]
fn parse_string() {
let file = ManuallyDrop::new(File::open("tests/string.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let obj = parse_next_token();
println!("{:?}", obj);
}
}
#[test]
fn parse_atom() {
let file = ManuallyDrop::new(File::open("tests/atom.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let obj = parse_next_token();
println!("{:?}", obj);
}
}
#[test]
fn parse_pair() {
let file = ManuallyDrop::new(File::open("tests/pair.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let obj = parse_next_token();
println!("{:?}", obj);
}
}
#[test]
fn parse_big_number() {
let file = ManuallyDrop::new(File::open("tests/big-number.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let obj = parse_next_token();
println!("{:?}", obj);
}
}
#[test]
fn global_env() {
unsafe {
let env = unsafe { *ENV_INIT };
println!("{:?}", DebugObjectExplicitGenv(env));
}
}
#[test]
fn eval_add() {
let file = ManuallyDrop::new(File::open("tests/add.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_let() {
let file = ManuallyDrop::new(File::open("tests/let.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_letstar() {
let file = ManuallyDrop::new(File::open("tests/letstar.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_print() {
let file = ManuallyDrop::new(File::open("tests/print.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_if() {
let file = ManuallyDrop::new(File::open("tests/if.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_define() {
let file = ManuallyDrop::new(File::open("tests/define.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_typeof() {
let file = ManuallyDrop::new(File::open("tests/typeof.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_eval() {
let file = ManuallyDrop::new(File::open("tests/eval.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_comment() {
let file = ManuallyDrop::new(File::open("tests/comment.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
eprintln!("parsed: {:?}", expr);
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_mapcar() {
let file = ManuallyDrop::new(File::open("tests/mapcar.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_setq() {
let file = ManuallyDrop::new(File::open("tests/setq.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn eval_equal() {
let file = ManuallyDrop::new(File::open("tests/equal.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
#[test]
fn complex() {
let file = ManuallyDrop::new(File::open("tests/print-args.l").unwrap());
unsafe {
init_source(&raw mut IFILE, file.as_raw_fd());
let expr = parse_next_token();
let env = unsafe { *ENV_INIT };
let result = eval(expr, env);
eprint!("done: ");
eprintln!("{result:?}");
}
}
}

1
stages/lisp0/tests/add.l Normal file
View file

@ -0,0 +1 @@
(+ 1 2 3)

View file

@ -0,0 +1 @@
'shrigma

View file

@ -0,0 +1 @@
(0xdeadcafebabe 11223344556677)

View file

@ -0,0 +1,2 @@
;; this is a comment
(define x 10) ; this is another comment

View file

@ -0,0 +1,6 @@
(progn
(define gt (lambda (a b)
(if (< b a)
't ())))
(gt 1 2)
)

View file

@ -0,0 +1 @@
(= 1 1 1 1)

View file

@ -0,0 +1,3 @@
(let ((x 10)
(y 20))
(eval '(cons x y)))

1
stages/lisp0/tests/if.l Normal file
View file

@ -0,0 +1 @@
(if () 1 2)

3
stages/lisp0/tests/let.l Normal file
View file

@ -0,0 +1,3 @@
(let ((x 10)
(y 20))
(+ x y))

View file

@ -0,0 +1,3 @@
(let* ((x 10)
(y (+ x 10)))
(+ x y))

View file

@ -0,0 +1,2 @@
(1 2 3)

View file

@ -0,0 +1,4 @@
(progn
(define inc (lambda (x) (+ x 1)))
(mapcar 'inc '(1 2 3 4 5))
)

View file

@ -0,0 +1 @@
(1 . 2)

View file

@ -0,0 +1,27 @@
(define print
(lambda (msg)
(let*
((msg-parts (str-parts msg))
(ptr (car msg-parts))
(len (cdr msg-parts))
(fd 1))
(syscall 1 fd ptr len)
)))
(define println
(lambda (msg)
(progn
(print msg)
(print "\n"))))
(define print-list (lambda (args)
(if (nil? args)
()
(mapcar 'println args))))
;; (print-list argv)
(println "Arguments: ")
(progn
(print-list argv)
)

View file

@ -0,0 +1,7 @@
(let*
((msg "Hello, World!\n")
(msg-parts (str-parts msg))
(ptr (car msg-parts))
(len (cdr msg-parts))
(fd 1))
(syscall 1 fd ptr len))

View file

@ -0,0 +1 @@
'(1 2 3 4)

View file

@ -0,0 +1,5 @@
(progn
(define x 10)
(setq x 20)
(eval 'x)
)

View file

@ -0,0 +1 @@
("hello, World!" \a \b \NL \Space)

View file

@ -0,0 +1,12 @@
(progn
(define exit (lambda (code)
(syscall 60 code)))
(define assert (lambda (cond)
(if (nil? cond)
(exit 1) ())))
(assert (= (typeof 1) 'NUMBER))
(assert (= (typeof 'symbol) 'ATOM))
(assert (= (typeof '()) 'NIL))
(assert (= (typeof '(1 2 3)) 'CONS))
(assert (= (typeof exit) 'CLOSURE))
)

8
stages/shared/Makefile Normal file
View file

@ -0,0 +1,8 @@
lib.o: lib.asm
nasm -g -f elf64 -o lib.o lib.asm
alloc.o: alloc.asm
nasm -g -f elf64 -o alloc.o alloc.asm
vec.o: vec.asm
nasm -g -f elf64 -o vec.o vec.asm

253
stages/shared/alloc.asm Normal file
View file

@ -0,0 +1,253 @@
;; Allocator
global alloc_pages
global dealloc_pages
global realloc_pages
global heap_alloc
global heap_dealloc
global heap_realloc
extern memcpy
section .bss
heap resq 1
section .text
oom_msg: db "Out of memory!", 10
oom:
lea rdi, [abs oom_msg]
mov esi, 15
mov rax, 1 ; syscall: write
mov rdi, 2 ; fd: stderr
syscall
mov rax, 60 ; syscall: exit
mov rdi, -1 ; status: -1
syscall
;; allocates $rdi bytes worth of pages via mmap
alloc_pages:
mov rax, 9 ; syscall: mmap
mov rsi, rdi ; length: rdi
xor rdi, rdi ; addr: NULL
mov rdx, 3 ; prot: PROT_READ | PROT_WRITE
mov r10, 34 ; flags: MAP_PRIVATE | MAP_ANONYMOUS
mov r8, -1 ; fd: -1
xor r9, r9 ; offset: 0
syscall
cmp rax, -1
jle oom
ret
dealloc_pages:
mov rax, 11 ; syscall: munmap
mov rdi, rsi ; addr: rsi
mov rsi, rdx ; length: rdx
syscall
cmp rax, -1
jle oom
ret
;; reallocates memory at $rdi[..$rsi] to a new location of size $rdx.
realloc_pages:
push rbp
mov rbp, rsp
sub rsp, 32
mov qword [rsp], rsi
mov qword [rsp + 8], rdi
mov rdi, rdx
call alloc_pages
mov rsi, rdi
mov rdi, qword [rsp + 8]
mov rdx, qword [rsp]
mov qword [rsp + 16], rax
call memcpy
mov rdi, qword [rsp + 8]
mov rsi, qword [rsp]
call dealloc_pages
mov rax, qword [rsp + 16]
add rsp, 32
pop rbp
ret
make_slab:
push rdi
mov rdi, 0x4000
call alloc_pages
mov qword [rax + 0x4000 - 8], 0 ; initialize the tail pointer to NULL
pop rdi
mov qword [rdi], 0 ; tail_end = 0
mov qword [rdi + 8], 0 ; free = NULL
mov qword [rdi + 16], rax ; first_block = allocated
mov qword [rdi + 24], rax ; last_block = allocated
mov rax, rdi
ret
init_heap:
push r14
xor r14, r14
mov rax, qword [rel heap]
test rax, rax
jnz .done
mov rdi, 0x1000
call alloc_pages
mov qword [rel heap], rax
.loop:
cmp r14, 9
jge .done
mov rdi, r14
shl rdi, 5 ; idx * 32
mov rax, qword [rel heap]
lea rdi, [rax + rdi] ; &heap.slabs[idx]
call make_slab
inc r14
jmp .loop
.done:
pop r14
ret
;; finds the correct slab for an allocation with size $rdi and align $rsi
slab_bucket:
xor rax, rax
dec rdi ; if size is a power of two, dec so we can later inc
dec rsi ; ^^
or rdi, rsi ; we only care about the log2, so just gather all the bits
bsr rsi, rdi ; log2((size-1) | (align-1))
sub rsi, 2 ; +1 for the dec, -3 to collapse the first 3 slabs into one
cmovae rax, rsi ; saturating sub
ret
slab_alloc:
push rbx
mov rax, qword [rdi + 8] ; free
test rax, rax
jz .no_free
mov rdx, qword [rax] ; next free chunk
mov qword [rdi + 8], rdx ; free = next
pop rbx
ret
.no_free:
add esi, 3 ; undo the -3 from slab_bucket to get the actual log2(size)
and esi, 63 ; clamp for safety
mov edx, 16376 ; 0x4000 - 8
mov ecx, esi
shr rdx, cl ; 0x4000 - 8 >> log2(size)
mov rax, qword [rdi] ; tail_end
mov rbx, qword [rdi + 24] ; last_block
cmp rax, rdx
jb .alloc_from_block
push rsi
push rax
push rdi
mov rdi, 0x4000 ; allocate a new block
call alloc_pages
mov qword [rax + 0x4000 - 8], 0 ; initialize the tail pointer to NULL
pop rdi
mov rsi, qword [rdi + 24] ; last_block
mov qword [rsi + 0x4000 - 8], rax
mov qword [rdi + 24], rax ; last_block = new block
mov rbx, rax
pop rax
pop rsi
.alloc_from_block:
inc qword [rdi] ; tail_end++
mov ecx, esi
shl rax, cl
add rax, rbx
pop rbx
ret
;; allocate a chunk of memory of size $rdi and alignment $rsi
heap_alloc:
mov rax, qword [rel heap]
cmp rax, 0
je .init
.is_init:
push rdi
call slab_bucket
cmp rax, 9
jge .mmap
mov rsi, rax
shl rax, 5 ; idx * 32
mov rdi, qword [rel heap]
lea rdi, [rdi + rax] ; &heap.slabs[idx]
call slab_alloc
pop rdi
ret
.init:
push rdi
push rsi
call init_heap
pop rsi
pop rdi
jmp .is_init
.mmap:
pop rdi
call alloc_pages
ret
;; deallocates a chunk of memory at $rdi of size $rsi and alignment $rdx
heap_dealloc:
push rbp
mov rbp, rsp
push rsi
push rdi
mov rdi, rsi
mov rsi, rdx
call slab_bucket
cmp rax, 9
jge .mmap
mov rsi, rax
shl rax, 5 ; idx * 32
mov rdi, qword [rel heap]
lea rdi, [rdi + rax] ; &heap.slabs[idx]
mov rax, qword [rdi + 8] ; free
pop rsi
mov qword [rsi], rax
mov qword [rdi + 8], rsi
pop rsi
pop rbp
ret
.mmap:
pop rdi
pop rsi
call dealloc_pages
pop rbp
ret
;; reallocates a chunk of memory at $rdi of size $rsi and alignment $rdx to a new location of size $rcx
heap_realloc:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov rbx, rdi
mov r12, rsi
mov r13, rdx
mov r14, rcx
mov rdi, r14
mov rsi, r13
call heap_alloc
mov r14, rax
mov rdi, rbx
mov rsi, rax
mov rdx, r12
call memcpy
mov rdi, rbx
mov rsi, r12
mov rdx, r13
call heap_dealloc
mov rax, r14
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret

428
stages/shared/alloc.hex1 Normal file
View file

@ -0,0 +1,428 @@
@oom_msg:
4f 75 74 20 6f 66 20 6d 65 6d 6f 72 79 21 0a
@oom:
# lea rdi, ['oom_msg]
48 8d 3d 'oom_msg
# mov esi, 15
be 0f 00 00 00
# mov eax, 1
b8 01 00 00 00
# mov edi, 2
bf 02 00 00 00
# syscall
0f 05
# mov eax, 60
b8 3c 00 00 00
# mov rdi, -1
48 c7 c7 ff ff ff ff
# syscall
0f 05
@alloc_pages:
# mov rax, 9
b8 09 00 00 00
# mov rsi, rdi
48 89 fe
# xor rdi, rdi
48 31 ff
# mov edi, 3
ba 03 00 00 00
# mov r10, 0x22
41 ba 22 00 00 00
# mov r8, -1
49 c7 c0 ff ff ff ff
# xor r9, r9
4d 31 c9
# syscall
0f 05
# cmp rax, -1
48 83 f8 ff
# jle 'oom
0f 8e 'oom
# ret
c3
@dealloc_pages:
# mov rax, 11
b8 0b 00 00 00
# mov rdi, rsi
48 89 f7
# mov rsi, rdx
48 89 d6
# syscall
0f 05
# cmp rax, -1
48 83 f8 ff
# jle 'oom
0f 8e 'oom
# ret
c3
@realloc_pages:
# push rbp
55
# mov rbp, rsp
48 89 e5
# sub rsp, 32
48 83 ec 20
# mov qword [rsp], rsi
48 89 34 24
# mov qword [rsp + 8], rdi
48 89 7c 24 08
# mov rdi, rdx
48 89 d7
# call 'alloc_pages
e8 'alloc_pages
# mov rsi, rax
48 89 fe
# mov rdi, qword [rsp + 8]
48 8b 7c 24 08
# mov rdx, qword [rsp]
48 8b 14 24
# mov qword [rsp + 16], rax
48 89 44 24 10
# call 'memcpy
e8 'memcpy
# mov rdi, qword [rsp + 8]
48 8b 7c 24 08
# mov rsi, qword [rsp]
48 8b 34 24
# call 'dealloc_pagesA
e8 'dealloc_pages
# mov rax, qword [rsp + 16]
48 8b 44 24 10
# add rsp, 32
48 83 c4 20
# pop rbp
5d
# ret
c3
@make_slab:
# push rdi
57
# mov rdi, 0x4000
bf 00 40 00 00
# call 'alloc_pages
e8 'alloc_pages
# mov qword [rsp + 0x4000 - 8], 0
48 c7 80 f8 3f 00 00 00 00 00 00
# pop rdi
5f
# mov qword [rdi], 0
48 c7 07 00 00 00 00
# mov qword [rdi + 8], 0
48 c7 47 08 00 00 00 00
# mov qword [rdi + 16], rax
48 89 47 10
# mov qword [rdi + 24], rax
48 89 47 18
# mov rax, rdi
48 89 f8
# ret
c3
@heap:
00 00 00 00 00 00 00 00
@init_heap.done:
# pop r14
41 5e
# ret
c3
@init_heap:
# push r14
41 56
# xor r14, r14
4d 31 f6
# mov rax, ['heap]
48 8b 05 'heap
# test rax, rax
48 85 c0
# jne .done
0f 85 'init_heap.done
# mov rdi, 0x1000
bf 00 10 00 00
# call alloc_pages
e8 'alloc_pages
# mov ['heap], rax
48 89 05 'heap
@init_heap.loop:
# cmp r14, 9
49 83 fe 09
# jge .done
0f 8d 'init_heap.done
# mov rdi, r14
4c 89 f7
# shl rdi, 12
48 c1 e7 05
# mov rax, ['heap]
48 8b 05 'heap
# lea rdi, [rax + rdi]
48 8d 3c 38
# call make_slab
e8 'make_slab
# inc r14
49 ff c6
# jmp .loop
e9 'init_heap.loop
@slab_bucket:
# xor eax, eax
31 c0
# dec rdi
48 ff cf
# dec rsi
48 ff ce
# or rdi, rsi
48 09 f7
# bsr rsi, rdi
48 0f bd f7
# sub rsi, 2
48 83 ee 02
# cmovae rax, rsi
48 0f 43 c6
# ret
c3
@slab_alloc.alloc_from_block:
# inc qword [rdi]
48 ff 07
#mov ecx, esi
89 f1
# shl rax, cl
48 d3 e0
# add rax, rbx
48 01 d8
# pop rbx
5b
# ret
c3
@slab_alloc.no_free:
# add esi, 3
83 c6 03
# and esi, 63
83 e6 3f
# mov edx, 0x4000 - 8
ba f8 3f 00 00
# mov ecx, esi
89 f1
# shr rdx, cl
48 d3 ea
# mov rax, qword [rdi]
48 8b 07
# mov rbx, qword [rdi + 24]
48 8b 5f 18
# cmp rax, rdx
48 39 d0
# jb .alloc_from_block
0f 82 'slab_alloc.alloc_from_block
# push rsi
56
# push rax
50
# push rdi
57
# mov rdi, 0x4000
bf 00 40 00 00
# call 'alloc_pages
e8 'alloc_pages
# mov qword [rax + 0x4000 - 8], 0
48 c7 80 f8 3f 00 00 00 00 00 00
# pop rdi
5f
# mov rsi, qword [rdi + 24]
48 8b 77 18
# mov qword [rdi + 24], rax
48 89 86 f8 3f 00 00
# mov qword [rdi + 24], rax
48 89 47 18
# mov rbx, rax
48 89 c3
# pop rax
58
# pop rsi
5e
@slab_alloc:
# push rbx
53
# mov rax, qword [rdi + 8]
48 8b 47 08
# test rax, rax
48 85 c0
# je .no_free
0f 84 'slab_alloc.no_free
# mov rdx, qword [rax]
48 8b 10
# mov qword [rdi + 8], rdx
48 89 57 08
# pop rbx
5b
# ret
c3
@heap_alloc.mmap:
# pop rdi
5f
# call 'alloc_pages
e8 'alloc_pages
# ret
c3
@heap_alloc.is_init:
# push rdi
57
# call slab_bucket
e8 'slab_bucket
# cmp rax, 9
48 83 f8 09
# jge .mmap
0f 8d 'heap_alloc.mmap
# mov rsi, rax
48 89 c6
# shl rax, 5
48 c1 e0 05
# mov rdi, qword ['heap]
48 8b 3d 'heap
# lea rdi, [rdi + rax]
48 8d 3c 07
# call 'slab_alloc
e8 'slab_alloc
# pop rdi
5f
# ret
c3
@heap_alloc.init:
# push rdi
57
# push rsi
56
# call 'init_heap
e8 'init_heap
# pop rsi
5e
# pop rdi
5f
# jmp 'heap_alloc.is_init
e9 'heap_alloc.is_init
@heap_alloc:
# mov rax, qword ['heap]
48 8b 05 'heap
# cmp rax, 0
48 83 f8 00
# je .init
0f 84 'heap_alloc.init
# jmp 'heap_alloc.is_init
e9 'heap_alloc.is_init
@heap_dealloc.mmap:
# pop rdi
5f
# pop rsi
5e
# call 'dealloc_pages
e8 'dealloc_pages
# pop rbp
5d
# ret
c3
@heap_dealloc:
# push rbp
55
# mov rbp, rsp
48 89 e5
# push rsi
56
# push rdi
57
# mov rdi, rsi
48 89 f7
# mov rsi, rdx
48 89 d6
# call 'slab_bucket
e8 'slab_bucket
# cmp rax, 9
48 83 f8 09
# jge 'heap_dealloc.mmap
0f 8d 'heap_dealloc.mmap
# mov rsi, rax
48 89 c6
# shl rax, 5
48 c1 e0 05
# mov rdi, qword ['heap]
48 8b 3d 'heap
# lea rdi, [rdi + rax]
48 8d 3c 07
# mov rax, qword [rdi + 8]
48 8b 47 08
# pop rsi
5e
# mov qword [rsi], rax
48 89 06
# mov qword [rdi + 8], rsi
48 89 77 08
# pop rsi
5e
# pop rbp
5d
# ret
c3
@heap_realloc:
# push rbp
55
# mov rbp, rsp
48 89 e5
# push rbx
53
# push r12
41 54
# push r13
41 55
# push r14
41 56
# mov rbx, rdi
48 89 fb
# mov r12, rsi
49 89 f4
# mov r13, rdx
49 89 d5
# mov r14, rcx
49 89 ce
# mov rdi, r14
4c 89 f7
# mov rsi, r13
4c 89 ee
# call 'heap_alloc
e8 'heap_alloc
# mov r14, rax
49 89 c6
# mov rdi, rbx
48 89 df
# mov rsi, rax
48 89 c6
# mov rdx, r12
4c 89 e2
# call 'memcpy
e8 'memcpy
# mov rdi, rbx
48 89 df
# mov rsi, r12
4c 89 e6
# mov rdx, r13
4c 89 ea
# call 'heap_dealloc
e8 'heap_dealloc
# mov rax, r14
4c 89 f0
# pop r14
41 5e
# pop r13
41 5d
# pop r12
41 5c
# pop rbx
5b
# pop rbp
5d
# ret
c3

343
stages/shared/lib.asm Normal file
View file

@ -0,0 +1,343 @@
;; Common Library Functions
global strlen
global strstr
global strcmp
global strfind
global memcpy
global is_alpha
global is_digit
global is_alphanumeric
global is_id_start
global is_id_cont
global to_digit
global parse_i64
global parse_i64_cstr
global exit
global fasthash
section .text
;; fn strlen(s: *const u8) -> usize
strlen:
xor eax, eax
.loop:
cmp byte [rdi + rax], 0
je .done
inc rax
jmp .loop
.done:
ret
;; find in the first $rsi bytes of $rdi (haystack) the first $rcx bytes of $rdx (needle).
;; returns the index in $rax, or -1 if not found.
;; fn strstr(haystack: *const u8, haystack_len: usize, needle: *const u8, needle_len: usize) -> isize
strstr:
push rbx
push r12
push r13
mov r13, rdi ; save original haystack pointer
mov rax, rsi ; remaining haystack length
.loop_haystack:
cmp rax, rcx
jb .not_found ; not enough haystack left for needle
xor rbx, rbx ; needle index
.loop_needle:
cmp rbx, rcx
jz .found ; found the needle
mov r12b, byte [rdx + rbx] ; needle[needle_index]
cmp r12b, byte [rdi + rbx] ; haystack[haystack_index + needle_index]
jne .next_haystack ; mismatch, move to next haystack index
inc rbx
jmp .loop_needle
.next_haystack:
inc rdi ; move to next haystack index
dec rax ; decrease remaining haystack length
jmp .loop_haystack
.not_found:
xor eax, eax
dec rax
jmp .done
.found:
sub rdi, r13 ; calculate the index of the found needle
mov rax, rdi
.done:
pop r13
pop r12
pop rbx
ret
;; fn strcmp(s1: *const u8, s1_len: usize, s2: *const u8, s2_len: usize) -> i32
strcmp:
cmp rcx, rsi
cmovb rsi, rcx ; if rhs is shorter, use its length for the loop
xor eax, eax
.strcmp_loop:
cmp rsi, rax
jz .strcmp_equal
movzx ecx, byte [rdx + rax]
cmp byte [rdi + rax], cl
lea rax, [rax + 1]
je .strcmp_loop
seta al ; al = lhs > rhs
sbb al, 0 ; al = al - CF
ret
.strcmp_equal:
xor eax, eax
ret
;; fn strfind(s: *const u8, len: usize, c: u8) -> isize
strfind:
strch:
xor eax, eax
.loop:
cmp rax, rsi
jge .not_found
mov cl, byte [rdi + rax]
cmp cl, dl
je .found
inc rax
jmp .loop
.not_found:
xor eax, eax
dec rax
.found:
ret
;; fn memcpy(src: *const u8, dst: *mut u8, len: usize)
memcpy:
.loop:
test rdx, rdx
jz .done
mov al, byte [rdi]
mov byte [rsi], al
inc rsi
inc rdi
dec rdx
jmp .loop
.done:
ret
;; fn is_alpha(c: u8) -> bool
is_alpha:
movzx eax, dil
and eax, 0x1fffdf ; ignore bit 5 (case)
add eax, -65 ; subtract 'A'
cmp eax, 26 ; check if in range 0-25
setb al
ret
;; fn is_digit(c: u8) -> bool
is_digit:
movzx edi, dil
lea eax, [rdi - 48] ; subtract '0'
cmp eax, 10 ; check if in range 0-9
setb al
ret
;; fn is_alphanumeric(c: u8) -> bool
is_alphanumeric:
lea eax, [rdi - 48]
cmp eax, 10
setb cl
and dil, 0xdf
add dil, -65
cmp dil, 26
setb al
or al, cl
ret
;; fn is_id_start(c: u8) -> bool
is_id_start:
call is_alpha
cmp dil, 95
sete cl
or al, cl
ret
;; fn is_id_continue(c: u8) -> bool
is_id_cont:
call is_alphanumeric
cmp dil, 45
sete cl
or al, cl
cmp dil, 95
sete cl
or al, cl
ret
;; converts char $dil to a digit with radix $rsi, returning it in $edx. $al is set to 1 if the char is a valid digit, and 0 otherwise.
;; fn to_digit(c: u8, radix: u8) -> (is_valid: bool, digit: u8)
to_digit:
lea eax, [rsi - 2]
cmp eax, 35
jae .invalid
movzx rdi, dil
lea edx, [rdi - 65] ; 'A' = 65
and edx, -33 ; convert to uppercase
add edx, 10 ; 'A' should map to 10
lea eax, [rdi - 48] ; '0' = 48
cmp esi, 11
cmovb edx, eax ; if radix <= 10, then take the difference from '0'
cmp edi, 58
cmovb edx, eax ; or if char < '9', then take the difference from '0'
xor eax, eax
cmp edx, esi
setb al ; al = edx < radix
ret
.invalid:
xor eax, eax
ret
;; fn parse_i64(s: *const u8) -> (is_valid: bool, value: i64)
parse_i64_cstr:
call strlen
mov rsi, rax
jmp parse_i64
;; fn parse_i64(s: *const u8, len: usize) -> (is_valid: bool, value: i64)
parse_i64:
push rbp
mov rbp, rsp
push rbx ; buf
push r12 ; radix
push r13 ; acc
push r14 ; end
push rdi
sub rsp, 8
mov dword [rsp], 1 ; sign = 1
mov rbx, rdi ; buf
mov r12, 10 ; radix
xor r13, r13 ; acc
xor eax, eax
mov r14, rsi
add r14, rdi ; end = buf + len
cmp rbx, r14
jge .done ; if buf >= end, return 0
; eat leading -
cmp byte [rbx], '-'
sete al
lea rbx, [rbx + rax]
shl eax, 1
sub dword [rsp], eax
cmp rbx, r14
jge .done
; eat leading +
cmp byte [rbx], '+'
sete al
lea rbx, [rbx + rax]
cmp rbx, r14
jge .done
mov dil, byte [rbx]
mov esi, 10
call to_digit
jnb .not_numeric
; radix
cmp dil, '0'
jne .loop
inc rbx
cmp rbx, r14
jge .done
mov dil, byte [rbx]
and dil, 0xdf ; convert to uppercase
cmp dil, 'X'
sete al
lea ecx, [eax + eax * 2]
shl ecx, 1 ; multiply by 6
add r12d, ecx ; radix += 'x' ? 6 : 0
cmp dil, 'O'
sete al
shl al, 1 ; multiply by 2
sub r12d, eax ; radix -= 'o' ? 2 : 0
cmp dil, 'B'
sete al
shl al, 3 ; multiply by 8
sub r12d, eax ; radix -= 'b' ? 8 : 0
cmp r12d, 10
setne al
lea rbx, [rbx + rax] ; skip the radix prefix if it was present
.loop:
cmp rbx, r14
jge .done
mov dil, byte [rbx]
inc rbx
cmp dil, '_'
je .loop
mov esi, r12d
call to_digit
mov ecx, edx
test al, al
jz .done
mov rax, r13
imul r12
add rax, rcx
mov r13, rax
jmp .loop
.not_numeric:
xor eax, eax
xor edx, edx
add rsp, 8
pop rdi
jmp .epilogue
.done:
mov rdx, rax
neg r13
cmp dword [rsp], 0
cmovl rdx, r13
add rsp, 8
pop rdi
sub rbx, rdi
mov rax, rbx
.epilogue:
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
;; fn exit() -> !
exit:
xor edi, edi
mov rax, 60
syscall
;; fn fasthash(s: *const u8, len: usize) -> u32
fasthash:
mov eax, 0x811C9DC5 ; FNV-1a 32-bit offset basis
.loop:
test rsi, rsi
jz .done
movzx ecx, byte [rdi]
xor eax, ecx
imul eax, 0x01000193 ; FNV prime
inc rdi
dec rsi
jmp .loop
.done:
ret

449
stages/shared/lib.hex1 Normal file
View file

@ -0,0 +1,449 @@
@strlen.done:
# ret
c3
@strlen:
# xor eax, eax
31 c0
@strlen.loop:
# cmp byte [rdi + rax], 0
80 3c 07 00
# je .done:
0f 84 'strlen.done
# inc rax
48 ff c0
# jmp .loop
e9 'strlen.loop
@strstr.found:
# sub rdi, r13
4c 29 ef
# mov rax, rdi
48 89 f8
@strstr.done:
# pop r13
41 5d
# pop r12
41 5c
# pop rbx
5b
# ret
c3
@strstr.notfound
# xor eax, eax
31 c0
# dec rax
48 ff c8
# jmp .done
e9 'strstr.done
@strstr.next_hackstack:
# inc rdi
48 ff c7
# dec rax
48 ff c8
@strstr.loop_haystack:
# cmp rax, rcx
48 39 c8
# jb 'strstr.notfound
0f 82 'strstr.notfound
# xor ebx, ebx
31 db
@strstr.loop_needle:
# cmp rbx, rcx
48 39 cb
# je .found
0f 84 'strstr.found
# mov r12b, byte [rdx + rbx]
44 8a 24 1a
# cmp r12b, byte [rdi + rbx]
44 3a 24 1f
# jne .next_haystack
0f 85 'strstr.next_haystack
# inc rbx
48 ff c3
# jmp .loop_needle
e9 'strstr.loop_needle
@strstr:
# push rbx
53
# push r12
41 54
# push r13
41 55
# mov r13, rdi
49 89 fd
# mov rax, rsi
48 89 f0
# jmp .loop_haystack
e9 'strstr.loop_haystack
@strcmp.eq:
31 c0
c3
@strcmp:
# cmp rcx, rsi
48 39 f1
# cmovb rsi, rcx
48 0f 42 f1
# xor eax, eax
31 c0
@strcmp.loop:
# cmp rsi, rax
48 39 c6
# je .eq
0f 84 'strcmp.eq
# movzx ecx, byte [rdx + rax]
0f b6 0c 02
# cmp byte [rdi + rax, cl
38 0c 07
# inc rax
48 ff c0
# je .loop
0f 84 'strcmp.loop
# seta al
0f 97 c0
# sbb al, 0
1c 00
# ret
c3
@strfind.nf:
# xor eax, eax
31 c0
# dec rax
48 ff c8
@strfind.f:
c3
@strfind:
# xor eax, eax
31 c0
# cmp rax, rsi
48 39 f0
# jge .nf
0f 8d 'strfind.nf
# mov cl, byte [rdi + rax]
8a 0c 07
# cmp cl, dl
38 d1
# je .f
0f 84 'strfind.f
# inc rax
48 ff c0
# jmp strfind
e9 'strfind
@memcpy.done:
c3
@memcpy:
# test rdx, rdx
48 85 d2
# jz .done
0f 84 'memcpy.done
# mov al, byte [rdi]
8a 07
# mov byte [rsi], al
88 06
# inc rsi
48 ff c6
# inc rdi
48 ff c7
# dec rdx
48 ff ca
# jmp .loop
e9 'memcpy
@is_alpha:
40 0f b6 c7
25 df ff 1f 00
83 c0 bf
83 f8 1a
0f 92 c0
c3
@is_digit:
40 0f b6 ff
8d 47 d0
83 f8 0a
0f 92 c0
c3
@is_alphanumeric:
8d 47 d0
83 f8 0a
0f 92 c1
40 80 e7 df
40 80 c7 bf
40 80 ff 1a
0f 92 c0
08 c8
c3
@is_id_start:
# call is_alpha
e8 'is_alpha
40 80 ff 5f
0f 94 c1
08 c8
c3
@is_id_cont:
# call is_alphanumeric
e8 'is_alphanumeric
40 80 ff 2d
0f 94 c1
08 c8
40 80 ff 5f
0f 94 c1
08 c8
c3
@to_digit.inv:
# xor eax, eax
31 c0
# ret
c3
@to_digit:
8d 46 fe
83 f8 23
# jae .inv
0f 83 'to_digit.inv
48 0f b6 ff
8d 57 bf
83 e2 df
83 c2 0a
8d 47 d0
83 fe 0b
0f 42 d0
83 ff 3a
0f 42 d0
31 c0
39 f2
0f 92 c0
c3
@parse_i64.done:
# mov rdx, rax
48 89 c2
# neg r13
49 f7 dd
# cmp dword [rsp], 0
83 3c 24 00
# cmovl rdx, r13
49 0f 4c d5
# add rsp, 8
48 83 c4 08
# pop rdi
5f
# sub rbx, rdi
48 29 fb
# mov rax, rbx
48 89 d8
@parse_i64.epilogue:
# pop r14
41 5e
# pop r13
41 5d
# pop r12
41 5c
# pop rbx
5b
# pop rbp
5d
# ret
c3
@parse_i64.not_num:
# xor eax, eax
31 c0
# xor edx, edx
31 d2
# add rsp, 8
48 83 c4 08
# pop rdi
5f
# jmp .epilogue
e9 'parse_i64.epilogue
@parse_i64_cstr:
e8 'strlen
# mov rsi, rax
48 89 c6
@parse_i64:
# push rbp
55
# mov rbp, rsp
48 89 e5
# push rbx
53
# push r12
41 54
# push r13
41 55
# push r14
41 56
# push rdi
57
# sub rsp, 8
48 83 ec 08
# mov dword [rsp], 1
c7 04 24 01 00 00 00
# mov rbx, rdi
48 89 fb
# mov r12, 10
41 bc 0a 00 00 00
# xor r13, r13
4d 31 ed
# xor eax, eax
31 c0
# mov r14, rsi
49 89 f6
# add r14, rdi
49 01 fe
# cmp rbx, r14
4c 39 f3
# jge .done
0f 8d 'parse_i64.done
# cmp byte [rbx], '-'
80 3b 2d
# sete al
0f 94 c0
# lea rbx, [rbx + rax]
48 8d 1c 03
# shl eax, 1
d1 e0
# sub dword [rsp], eax
29 04 24
# cmp rbx, r14
4c 39 f3
# jge .done
0f 8d 'parse_i64.done
# cmp byte [rbx], '+'
80 3b 2b
# sete al
0f 94 c0
# lea rbx, [rbx + rax]
48 8d 1c 03
# cmp rbx, r14
4c 39 f3
# jge .done
0f 8d 'parse_i64.done
# mov dil, byte [rbx]
40 8a 3b
# mov esi, 10
be 0a 00 00 00
# call to_digit
e8 'to_digit
# jae .not_num
0f 83 'parse_i64.not_num
# cmp dil, '0'
40 80 ff 30
# jne .loop
0f 85 'parse_i64.loop
# inc rbx
48 ff c3
# cmp rbx, r14
4c 39 f3
# jge .done
0f 8d 'parse_i64.done
# mov dil, byte [rbx]
40 8a 3b
# and dil, 0xdf
40 80 e7 df
# cmp dil, 'X'
40 80 ff 58
# sete al
0f 94 c0
# lea ecx, [eax + eax * 2]
67 8d 0c 40
# shl ecx, 1
d1 e1
# add r12d, ecx
41 01 cc
# cmp dil, 'O'
40 80 ff 4f
# sete al
0f 94 c0
# shl al, 1
d0 e0
# sub r12d, eax
41 29 c4
# cmp dil, 'B'
40 80 ff 42
# sete al
0f 94 c0
# shl al, 3
c0 e0 03
# sub r12d, eax
41 29 c4
# cmp r12d, 10
41 83 fc 0a
# setne al
0f 95 c0
# lea rbx, [rbx + rax]
48 8d 1c 03
@parse_i64.loop:
# cmp rbx, r14
4c 39 f3
# jge .done
0f 8d 'parse_i64.done
# mov dil, byte [rbx]
40 8a 3b
# inc rbx
48 ff c3
# cmp dil, '_'
40 80 ff 5f
# je .loop
0f 84 'parse_i64.loop
# mov esi, r12d
44 89 e6
# call to_digit
e8 'to_digit
# mov ecx, edx
89 d1
# test al, al
84 c0
# je .done
0f 84 'parse_i64.done
# mov rax, r13
4c 89 e8
# imul r12
49 f7 ec
# add rax, rcx
48 01 c8
mov r13, rax
49 89 c5
# jmp .loop
e9 'parse_i64.loop
@exit:
# xor edi, edi
31 ff
# mov eax, 60
b8 3c 00 00 00
# syscall
0f 05
@fasthash.done:
c3
@fasthash:
# mov eax, 0x811c9dc5
b8 c5 9d 1c 81
@fasthash.loop:
# test rsi, rsi
48 85 f6
# jz .done
0f 84 'fasthash.done
# movzx ecx, byte [rdi]
0f b6 0f
# xor eax, ecx
31 c8
# imul eax, 0x01000193
69 c0 93 01 00 01
# inc rdi
48 ff c7
# dec rsi
48 ff ce
# jmp .loop
e9 'fasthash.loop

77
stages/shared/vec.asm Normal file
View file

@ -0,0 +1,77 @@
;; Vec / Dynamic Array Structure
extern heap_realloc
global vec_init
global vec_get_nth
global vec_reserve
struc Vec
.data resq 1 ; pointer to elements
.size resd 1 ; size in element count
.cap resd 1 ; cap in element count
.ele_size resd 1 ; size of each element in bytes
.ele_align resd 1 ; alignment of each element in bytes
endstruc
;; fn vec_init(vec: *mut Vec, ele_size: usize, ele_align: usize) -> *mut Vec
vec_init:
mov qword [rdi + Vec.data], 0
mov dword [rdi + Vec.size], 0
mov dword [rdi + Vec.cap], 0
mov dword [rdi + Vec.ele_size], esi
mov dword [rdi + Vec.ele_align], edx
mov rax, rdi
ret
;; fn vec_get_nth(vec: *const Vec, idx: usize) -> *const u8
vec_get_nth:
mov rax, rsi
mov ecx, dword [rdi + Vec.ele_size]
mul ecx
add rax, qword [rdi + Vec.data]
ret
;; reserve space for $rsi additional entries in vec $rdi.
;; fn vec_reserve(vec: *mut Vec, count: usize)
vec_reserve:
push rbp
mov rbp, rsp
sub rsp, 16
mov edx, dword [rdi + Vec.ele_size]
mov ecx, dword [rdi + Vec.ele_align]
mov dword [rsp], edx ; obj-size
mov dword [rsp + 4], ecx ; alignment
mov qword [rsp + 8], rdi ; table pointer
mov eax, dword [rdi + Vec.size]
add eax, esi
cmp eax, dword [rdi + Vec.cap]
jl .done
mov eax, esi
add eax, dword [rdi + Vec.size]
mov ecx, dword [rdi + Vec.cap]
mov edx, ecx
shl edx, 1
cmp eax, edx
cmovl eax, edx
; new cap
mul dword [rsp] ; obj-size
; old cap
xchg eax, ecx
mul dword [rsp] ; obj-size
mov rdi, qword [rdi + Vec.data] ; old pointer
mov esi, eax ; old size
mov edx, ecx ; new size
mov ecx, dword [rsp + 4] ; alignment
call heap_realloc
mov rdi, qword [rsp + 8] ; table pointer
mov qword [rdi + Vec.data], rax
.done:
add rsp, 16
pop rbp
ret

51
test.asm Normal file
View file

@ -0,0 +1,51 @@
section .text
global _start
_start:
mov rdi, 42
call main
mov rdi, rax
mov rax, 60 ; syscall: exit
syscall
global main
main:
push rbp
mov rbp, rsp
jmp .prologue
.args:
mov qword [rbp -8], rdi
.body:
mov rbx, 42
mov rcx, qword [rbp -8]
mov rax, rbx
cmp rcx, rax
sete bl
cmp bl, 0
je .Be
mov rbx, 7
mov qword [rbp -16], rbx
mov rdi, qword [rbp -16]
mov rax, rsp
sub rsp, 8
and rsp, -16
mov [rsp], rax
call main
pop rsp
mov rbx, rax
mov rax, rbx
jmp .epilogue
jmp .PHI4
.Be:
lea rax, main
jmp .epilogue
.PHI4:
.epilogue:
pop rbx
add rsp, 16
pop rbp
ret
.prologue:
sub rsp, 16
push rbx
jmp .args