Compare commits
2 Commits
1e7657ea2a
...
192cff8f8d
| Author | SHA1 | Date | |
|---|---|---|---|
| 192cff8f8d | |||
| 70640b3062 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
/target
|
||||
/out*
|
||||
/TODO
|
||||
/musl-*
|
||||
/vscode
|
||||
|
||||
@@ -6,9 +6,8 @@ A very cool language
|
||||
* Clean indentation-based syntax
|
||||
* Compiles to x86_64 Assembly
|
||||
* Growing [standard library](https://github.com/antpiasecki/zern/tree/main/src/std)
|
||||
* Produces tiny static executables (~30KB with musl)
|
||||
* Produces tiny static executables (11KB for `hello.zr`)
|
||||
* No libc required!
|
||||
* Sometimes works
|
||||
* Has the pipe operator
|
||||
|
||||
## Syntax
|
||||
|
||||
@@ -70,7 +70,7 @@ macro_rules! emit {
|
||||
static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
|
||||
|
||||
// TODO: currently they are all just 64 bit values
|
||||
static BUILTIN_TYPES: [&str; 6] = ["void", "u8", "i64", "str", "bool", "ptr"];
|
||||
static BUILTIN_TYPES: [&str; 7] = ["void", "u8", "i64", "str", "bool", "ptr", "any"];
|
||||
|
||||
pub struct CodegenX86_64<'a> {
|
||||
output: String,
|
||||
@@ -100,7 +100,7 @@ impl<'a> CodegenX86_64<'a> {
|
||||
format!("section .data\n{}{}", self.data_section, self.output)
|
||||
}
|
||||
|
||||
pub fn emit_prologue(&mut self) -> Result<(), ZernError> {
|
||||
pub fn emit_prologue(&mut self, emit_start: bool) -> Result<(), ZernError> {
|
||||
emit!(
|
||||
&mut self.output,
|
||||
"section .note.GNU-stack
|
||||
@@ -109,6 +109,7 @@ impl<'a> CodegenX86_64<'a> {
|
||||
section .bss
|
||||
_heap_head: resq 1
|
||||
_heap_tail: resq 1
|
||||
_environ: resq 1
|
||||
|
||||
section .text._builtin_heap_head
|
||||
_builtin_heap_head:
|
||||
@@ -141,14 +142,52 @@ _builtin_syscall:
|
||||
mov r9, [rsp+8]
|
||||
syscall
|
||||
ret
|
||||
"
|
||||
);
|
||||
|
||||
if emit_start {
|
||||
emit!(
|
||||
&mut self.output,
|
||||
"
|
||||
section .text._builtin_environ
|
||||
_builtin_environ:
|
||||
lea rax, [rel _environ]
|
||||
mov rax, [rax]
|
||||
ret
|
||||
|
||||
global _start
|
||||
section .text
|
||||
_start:
|
||||
xor rbp, rbp
|
||||
; setup args
|
||||
pop rdi
|
||||
mov rsi, rsp
|
||||
; save environ
|
||||
lea rdx, [rsi + rdi*8 + 8]
|
||||
lea rax, [rel _environ]
|
||||
mov [rax], rdx
|
||||
; align stack
|
||||
and rsp, -16
|
||||
; main()
|
||||
call main
|
||||
; exit
|
||||
mov rdi, rax
|
||||
mov rax, 60
|
||||
syscall
|
||||
"
|
||||
);
|
||||
} else {
|
||||
emit!(
|
||||
&mut self.output,
|
||||
"
|
||||
section .text._builtin_environ
|
||||
_builtin_environ:
|
||||
extern environ
|
||||
mov rax, [rel environ]
|
||||
ret
|
||||
"
|
||||
);
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@@ -63,7 +63,7 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
|
||||
|
||||
let mut analyzer = analyzer::Analyzer::new();
|
||||
let mut codegen = codegen_x86_64::CodegenX86_64::new(&mut analyzer);
|
||||
codegen.emit_prologue()?;
|
||||
codegen.emit_prologue(!args.use_gcc)?;
|
||||
compile_file_to(
|
||||
&mut codegen,
|
||||
"syscalls.zr",
|
||||
@@ -82,15 +82,15 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
|
||||
|
||||
run_command(format!("nasm -f elf64 -o {}.o {}.s", args.out, args.out));
|
||||
|
||||
if args.use_glibc {
|
||||
if args.use_gcc {
|
||||
run_command(format!(
|
||||
"gcc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}",
|
||||
args.out, args.out, args.cflags
|
||||
));
|
||||
} else {
|
||||
run_command(format!(
|
||||
"musl-gcc -static -o {} {}.o -flto -Wl,--gc-sections {}",
|
||||
args.out, args.out, args.cflags
|
||||
"ld -static -o {} {}.o --gc-sections -e _start",
|
||||
args.out, args.out
|
||||
));
|
||||
}
|
||||
|
||||
@@ -123,8 +123,8 @@ struct Args {
|
||||
#[arg(short = 'r', help = "Run the compiled executable")]
|
||||
run_exe: bool,
|
||||
|
||||
#[arg(short = 'm', help = "Use glibc instead of musl")]
|
||||
use_glibc: bool,
|
||||
#[arg(short = 'm', help = "Use gcc")]
|
||||
use_gcc: bool,
|
||||
|
||||
#[arg(short = 'C', default_value = "", help = "Extra flags to pass to gcc")]
|
||||
cflags: String,
|
||||
|
||||
@@ -28,13 +28,17 @@ func mem._split_block[blk: mem.Block, needed: i64] : void
|
||||
mem.write64(_builtin_heap_tail(), new_blk)
|
||||
|
||||
func mem._request_space[size: i64] : mem.Block
|
||||
let blk: mem.Block = _builtin_syscall(SYS_brk, 0)
|
||||
let new_brk: ptr = blk + MEM_BLOCK_SIZE + size
|
||||
let result: ptr = _builtin_syscall(SYS_brk, new_brk)
|
||||
if result != new_brk
|
||||
let needed: i64 = size + MEM_BLOCK_SIZE
|
||||
let alloc_size: i64 = (needed + 4095) & -4096
|
||||
|
||||
// PROT_READ | PROT_WRITE = 3
|
||||
// MAP_PRIVATE | MAP_ANONYMOUS = 34
|
||||
// fd = -1, offset = 0
|
||||
let blk: mem.Block = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0)
|
||||
if blk == -1
|
||||
return 0
|
||||
|
||||
blk->size = size
|
||||
blk->size = alloc_size - MEM_BLOCK_SIZE
|
||||
blk->free = false
|
||||
blk->next = 0
|
||||
|
||||
@@ -47,15 +51,36 @@ func mem._request_space[size: i64] : mem.Block
|
||||
|
||||
func mem._coalesce[] : void
|
||||
let cur: mem.Block = mem.read64(_builtin_heap_head())
|
||||
while cur && cur->next
|
||||
let prev: mem.Block = 0
|
||||
|
||||
while cur
|
||||
let next: mem.Block = cur->next
|
||||
if cur->free && next->free
|
||||
let expected_next: mem.Block = cur + MEM_BLOCK_SIZE + cur->size
|
||||
|
||||
if cur->free && next && next->free && expected_next == next
|
||||
cur->size = cur->size + MEM_BLOCK_SIZE + next->size
|
||||
cur->next = next->next
|
||||
if !cur->next
|
||||
mem.write64(_builtin_heap_tail(), cur)
|
||||
else
|
||||
continue
|
||||
|
||||
let block_total: i64 = cur->size + MEM_BLOCK_SIZE
|
||||
if cur->free && (cur & 4095) == 0 && (block_total & 4095) == 0
|
||||
if prev
|
||||
prev->next = cur->next
|
||||
if !cur->next
|
||||
mem.write64(_builtin_heap_tail(), prev)
|
||||
else
|
||||
mem.write64(_builtin_heap_head(), cur->next)
|
||||
if !cur->next
|
||||
mem.write64(_builtin_heap_tail(), 0)
|
||||
|
||||
_builtin_syscall(SYS_munmap, cur, block_total)
|
||||
cur = cur->next
|
||||
continue
|
||||
|
||||
prev = cur
|
||||
cur = cur->next
|
||||
|
||||
func mem.alloc[size: i64] : ptr
|
||||
if size == 0
|
||||
@@ -78,6 +103,8 @@ func mem.alloc[size: i64] : ptr
|
||||
if !mem.read64(_builtin_heap_head())
|
||||
mem.write64(_builtin_heap_head(), blk)
|
||||
|
||||
mem._split_block(blk, size)
|
||||
|
||||
return blk + MEM_BLOCK_SIZE
|
||||
|
||||
func mem.free[x: ptr] : void
|
||||
@@ -104,7 +131,9 @@ func mem.realloc[x: ptr, new_size: i64] : ptr
|
||||
return x
|
||||
|
||||
let next: mem.Block = blk->next
|
||||
if next && next->free
|
||||
let expected_next: mem.Block = blk + MEM_BLOCK_SIZE + blk->size
|
||||
|
||||
if next && next->free && expected_next == next
|
||||
let combined: i64 = blk->size + MEM_BLOCK_SIZE + next->size
|
||||
if combined >= new_size
|
||||
blk->size = combined
|
||||
@@ -199,7 +228,7 @@ func io.read_line[]: str
|
||||
func io.read_file[path: str] : str
|
||||
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
|
||||
if fd <= 0
|
||||
panic("failed to open file")
|
||||
panic("failed to open file") // TODO
|
||||
|
||||
let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2)
|
||||
_builtin_syscall(SYS_lseek, fd, 0, 0)
|
||||
@@ -217,7 +246,7 @@ struct io.Buffer
|
||||
func io.read_binary_file[path: str] : io.Buffer
|
||||
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
|
||||
if fd < 0
|
||||
panic("failed to open file")
|
||||
panic("failed to open file") // TODO
|
||||
|
||||
let buffer: io.Buffer = new io.Buffer
|
||||
buffer->size = _builtin_syscall(SYS_lseek, fd, 0, 2)
|
||||
@@ -231,7 +260,7 @@ func io.read_binary_file[path: str] : io.Buffer
|
||||
func io.write_file[path: str, content: str] : void
|
||||
let fd: ptr = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644)
|
||||
if fd < 0
|
||||
panic("failed to open file")
|
||||
panic("failed to open file") // TODO
|
||||
|
||||
_builtin_syscall(SYS_write, fd, content, str.len(content))
|
||||
_builtin_syscall(SYS_close, fd)
|
||||
@@ -373,7 +402,7 @@ func str.reverse[s: str] : str
|
||||
out[len] = 0
|
||||
return out
|
||||
|
||||
// not sure this covers all wacky edge cases
|
||||
// not sure this covers all the wacky edge cases
|
||||
func str.from_i64[n: i64] : str
|
||||
if n == 0
|
||||
return str.copy("0")
|
||||
@@ -559,17 +588,17 @@ struct Array
|
||||
func array.new[] : Array
|
||||
return new Array
|
||||
|
||||
func array.nth[xs: Array, n: i64] : i64
|
||||
func array.nth[xs: Array, n: i64] : any
|
||||
if n < 0 || n >= xs->size
|
||||
panic("array.nth out of bounds")
|
||||
return mem.read64(xs->data + n * 8)
|
||||
|
||||
func array.set[xs: Array, n: i64, x: i64] : void
|
||||
func array.set[xs: Array, n: i64, x: any] : void
|
||||
if n < 0 || n >= xs->size
|
||||
panic("array.set out of bounds")
|
||||
mem.write64(xs->data + n * 8, x)
|
||||
|
||||
func array.push[xs: Array, x: i64] : void
|
||||
func array.push[xs: Array, x: any] : void
|
||||
if xs->size == xs->capacity
|
||||
let new_capacity = 4
|
||||
if xs->capacity != 0
|
||||
@@ -585,10 +614,10 @@ func array.free[xs: Array] : void
|
||||
mem.free(xs->data)
|
||||
mem.free(xs)
|
||||
|
||||
func array.pop[xs: Array] : i64
|
||||
func array.pop[xs: Array] : any
|
||||
if xs->size == 0
|
||||
panic("array.pop on empty array")
|
||||
let x: i64 = array.nth(xs, xs->size - 1)
|
||||
let x: any = array.nth(xs, xs->size - 1)
|
||||
xs->size = xs->size - 1
|
||||
return x
|
||||
|
||||
@@ -632,7 +661,7 @@ func alg._partition[arr: Array, low: i64, high: i64] : i64
|
||||
array.set(arr, high, temp)
|
||||
return i + 1
|
||||
|
||||
func alg.count[arr: Array, item: i64] : i64
|
||||
func alg.count[arr: Array, item: any] : i64
|
||||
let count = 0
|
||||
for i in 0..arr->size
|
||||
if array.nth(arr, i) == item
|
||||
@@ -652,7 +681,7 @@ func alg.filter[arr: Array, fn: ptr] : Array
|
||||
array.push(out, array.nth(arr, i))
|
||||
return out
|
||||
|
||||
func alg.reduce[arr: Array, fn: ptr, acc: i64] : i64
|
||||
func alg.reduce[arr: Array, fn: ptr, acc: any] : any
|
||||
for i in 0..arr->size
|
||||
acc = fn(acc, array.nth(arr, i))
|
||||
return acc
|
||||
@@ -680,15 +709,15 @@ struct os.Timeval
|
||||
func os.time[] : i64
|
||||
let tv: os.Timeval = new os.Timeval
|
||||
_builtin_syscall(SYS_gettimeofday, tv, 0)
|
||||
let out: i64 = tv->tv_sec * 1000 + tv->tv_usec / 1000
|
||||
mem.free(tv)
|
||||
return tv->tv_sec * 1000 + tv->tv_usec / 1000
|
||||
return out
|
||||
|
||||
// voodoo magic
|
||||
func os.shell[command: str] : i64
|
||||
let pid: i64 = _builtin_syscall(SYS_fork)
|
||||
if pid == 0
|
||||
// leaky but not sure where can i free it
|
||||
let argv: Array = ["sh", "-c", command, 0]
|
||||
let argv: Array = ["sh", "-c", command, 0] // gets freed after child process exits
|
||||
_builtin_syscall(SYS_execve, "/bin/sh", argv->data, _builtin_environ())
|
||||
_builtin_syscall(SYS_exit, 1)
|
||||
else
|
||||
@@ -770,6 +799,7 @@ func net.listen[packed_host: i64, port: i64] : i64
|
||||
|
||||
return s
|
||||
|
||||
// TODO: resolve DNS
|
||||
func net.connect[packed_host: i64, port: i64] : i64
|
||||
let s: i64 = _builtin_syscall(SYS_socket, 2, 1, 0)
|
||||
if s < 0
|
||||
|
||||
Reference in New Issue
Block a user