Compare commits

..

2 Commits

Author SHA1 Message Date
192cff8f8d drop gcc by default, any type 2026-03-12 14:24:44 +01:00
70640b3062 use mmap instead of brk in mem.alloc to fix a glibc segfault 2026-03-12 14:23:57 +01:00
5 changed files with 102 additions and 35 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
/target /target
/out* /out*
/TODO /TODO
/musl-*
/vscode /vscode

View File

@@ -6,9 +6,8 @@ A very cool language
* Clean indentation-based syntax * Clean indentation-based syntax
* Compiles to x86_64 Assembly * Compiles to x86_64 Assembly
* Growing [standard library](https://github.com/antpiasecki/zern/tree/main/src/std) * 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! * No libc required!
* Sometimes works
* Has the pipe operator * Has the pipe operator
## Syntax ## Syntax

View File

@@ -70,7 +70,7 @@ macro_rules! emit {
static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
// TODO: currently they are all just 64 bit values // 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> { pub struct CodegenX86_64<'a> {
output: String, output: String,
@@ -100,7 +100,7 @@ impl<'a> CodegenX86_64<'a> {
format!("section .data\n{}{}", self.data_section, self.output) 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!( emit!(
&mut self.output, &mut self.output,
"section .note.GNU-stack "section .note.GNU-stack
@@ -109,6 +109,7 @@ impl<'a> CodegenX86_64<'a> {
section .bss section .bss
_heap_head: resq 1 _heap_head: resq 1
_heap_tail: resq 1 _heap_tail: resq 1
_environ: resq 1
section .text._builtin_heap_head section .text._builtin_heap_head
_builtin_heap_head: _builtin_heap_head:
@@ -141,14 +142,52 @@ _builtin_syscall:
mov r9, [rsp+8] mov r9, [rsp+8]
syscall syscall
ret 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 section .text._builtin_environ
_builtin_environ: _builtin_environ:
extern environ extern environ
mov rax, [rel environ] mov rax, [rel environ]
ret ret
" "
); );
}
Ok(()) Ok(())
} }

View File

@@ -63,7 +63,7 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
let mut analyzer = analyzer::Analyzer::new(); let mut analyzer = analyzer::Analyzer::new();
let mut codegen = codegen_x86_64::CodegenX86_64::new(&mut analyzer); let mut codegen = codegen_x86_64::CodegenX86_64::new(&mut analyzer);
codegen.emit_prologue()?; codegen.emit_prologue(!args.use_gcc)?;
compile_file_to( compile_file_to(
&mut codegen, &mut codegen,
"syscalls.zr", "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)); run_command(format!("nasm -f elf64 -o {}.o {}.s", args.out, args.out));
if args.use_glibc { if args.use_gcc {
run_command(format!( run_command(format!(
"gcc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}", "gcc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}",
args.out, args.out, args.cflags args.out, args.out, args.cflags
)); ));
} else { } else {
run_command(format!( run_command(format!(
"musl-gcc -static -o {} {}.o -flto -Wl,--gc-sections {}", "ld -static -o {} {}.o --gc-sections -e _start",
args.out, args.out, args.cflags args.out, args.out
)); ));
} }
@@ -123,8 +123,8 @@ struct Args {
#[arg(short = 'r', help = "Run the compiled executable")] #[arg(short = 'r', help = "Run the compiled executable")]
run_exe: bool, run_exe: bool,
#[arg(short = 'm', help = "Use glibc instead of musl")] #[arg(short = 'm', help = "Use gcc")]
use_glibc: bool, use_gcc: bool,
#[arg(short = 'C', default_value = "", help = "Extra flags to pass to gcc")] #[arg(short = 'C', default_value = "", help = "Extra flags to pass to gcc")]
cflags: String, cflags: String,

View File

@@ -28,13 +28,17 @@ func mem._split_block[blk: mem.Block, needed: i64] : void
mem.write64(_builtin_heap_tail(), new_blk) mem.write64(_builtin_heap_tail(), new_blk)
func mem._request_space[size: i64] : mem.Block func mem._request_space[size: i64] : mem.Block
let blk: mem.Block = _builtin_syscall(SYS_brk, 0) let needed: i64 = size + MEM_BLOCK_SIZE
let new_brk: ptr = blk + MEM_BLOCK_SIZE + size let alloc_size: i64 = (needed + 4095) & -4096
let result: ptr = _builtin_syscall(SYS_brk, new_brk)
if result != new_brk // 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 return 0
blk->size = size blk->size = alloc_size - MEM_BLOCK_SIZE
blk->free = false blk->free = false
blk->next = 0 blk->next = 0
@@ -47,15 +51,36 @@ func mem._request_space[size: i64] : mem.Block
func mem._coalesce[] : void func mem._coalesce[] : void
let cur: mem.Block = mem.read64(_builtin_heap_head()) 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 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->size = cur->size + MEM_BLOCK_SIZE + next->size
cur->next = next->next cur->next = next->next
if !cur->next if !cur->next
mem.write64(_builtin_heap_tail(), cur) 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 cur = cur->next
continue
prev = cur
cur = cur->next
func mem.alloc[size: i64] : ptr func mem.alloc[size: i64] : ptr
if size == 0 if size == 0
@@ -78,6 +103,8 @@ func mem.alloc[size: i64] : ptr
if !mem.read64(_builtin_heap_head()) if !mem.read64(_builtin_heap_head())
mem.write64(_builtin_heap_head(), blk) mem.write64(_builtin_heap_head(), blk)
mem._split_block(blk, size)
return blk + MEM_BLOCK_SIZE return blk + MEM_BLOCK_SIZE
func mem.free[x: ptr] : void func mem.free[x: ptr] : void
@@ -104,7 +131,9 @@ func mem.realloc[x: ptr, new_size: i64] : ptr
return x return x
let next: mem.Block = blk->next 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 let combined: i64 = blk->size + MEM_BLOCK_SIZE + next->size
if combined >= new_size if combined >= new_size
blk->size = combined blk->size = combined
@@ -199,7 +228,7 @@ func io.read_line[]: str
func io.read_file[path: str] : str func io.read_file[path: str] : str
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd <= 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) let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2)
_builtin_syscall(SYS_lseek, fd, 0, 0) _builtin_syscall(SYS_lseek, fd, 0, 0)
@@ -217,7 +246,7 @@ struct io.Buffer
func io.read_binary_file[path: str] : io.Buffer func io.read_binary_file[path: str] : io.Buffer
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0 if fd < 0
panic("failed to open file") panic("failed to open file") // TODO
let buffer: io.Buffer = new io.Buffer let buffer: io.Buffer = new io.Buffer
buffer->size = _builtin_syscall(SYS_lseek, fd, 0, 2) 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 func io.write_file[path: str, content: str] : void
let fd: ptr = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644) let fd: ptr = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644)
if fd < 0 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_write, fd, content, str.len(content))
_builtin_syscall(SYS_close, fd) _builtin_syscall(SYS_close, fd)
@@ -373,7 +402,7 @@ func str.reverse[s: str] : str
out[len] = 0 out[len] = 0
return out 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 func str.from_i64[n: i64] : str
if n == 0 if n == 0
return str.copy("0") return str.copy("0")
@@ -559,17 +588,17 @@ struct Array
func array.new[] : Array func array.new[] : Array
return 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 if n < 0 || n >= xs->size
panic("array.nth out of bounds") panic("array.nth out of bounds")
return mem.read64(xs->data + n * 8) 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 if n < 0 || n >= xs->size
panic("array.set out of bounds") panic("array.set out of bounds")
mem.write64(xs->data + n * 8, x) 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 if xs->size == xs->capacity
let new_capacity = 4 let new_capacity = 4
if xs->capacity != 0 if xs->capacity != 0
@@ -585,10 +614,10 @@ func array.free[xs: Array] : void
mem.free(xs->data) mem.free(xs->data)
mem.free(xs) mem.free(xs)
func array.pop[xs: Array] : i64 func array.pop[xs: Array] : any
if xs->size == 0 if xs->size == 0
panic("array.pop on empty array") 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 xs->size = xs->size - 1
return x return x
@@ -632,7 +661,7 @@ func alg._partition[arr: Array, low: i64, high: i64] : i64
array.set(arr, high, temp) array.set(arr, high, temp)
return i + 1 return i + 1
func alg.count[arr: Array, item: i64] : i64 func alg.count[arr: Array, item: any] : i64
let count = 0 let count = 0
for i in 0..arr->size for i in 0..arr->size
if array.nth(arr, i) == item 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)) array.push(out, array.nth(arr, i))
return out 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 for i in 0..arr->size
acc = fn(acc, array.nth(arr, i)) acc = fn(acc, array.nth(arr, i))
return acc return acc
@@ -680,15 +709,15 @@ struct os.Timeval
func os.time[] : i64 func os.time[] : i64
let tv: os.Timeval = new os.Timeval let tv: os.Timeval = new os.Timeval
_builtin_syscall(SYS_gettimeofday, tv, 0) _builtin_syscall(SYS_gettimeofday, tv, 0)
let out: i64 = tv->tv_sec * 1000 + tv->tv_usec / 1000
mem.free(tv) mem.free(tv)
return tv->tv_sec * 1000 + tv->tv_usec / 1000 return out
// voodoo magic // voodoo magic
func os.shell[command: str] : i64 func os.shell[command: str] : i64
let pid: i64 = _builtin_syscall(SYS_fork) let pid: i64 = _builtin_syscall(SYS_fork)
if pid == 0 if pid == 0
// leaky but not sure where can i free it let argv: Array = ["sh", "-c", command, 0] // gets freed after child process exits
let argv: Array = ["sh", "-c", command, 0]
_builtin_syscall(SYS_execve, "/bin/sh", argv->data, _builtin_environ()) _builtin_syscall(SYS_execve, "/bin/sh", argv->data, _builtin_environ())
_builtin_syscall(SYS_exit, 1) _builtin_syscall(SYS_exit, 1)
else else
@@ -770,6 +799,7 @@ func net.listen[packed_host: i64, port: i64] : i64
return s return s
// TODO: resolve DNS
func net.connect[packed_host: i64, port: i64] : i64 func net.connect[packed_host: i64, port: i64] : i64
let s: i64 = _builtin_syscall(SYS_socket, 2, 1, 0) let s: i64 = _builtin_syscall(SYS_socket, 2, 1, 0)
if s < 0 if s < 0