diff --git a/.gitignore b/.gitignore index f01f275..0aeca07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /target /out* /TODO -/musl-* /vscode diff --git a/README.md b/README.md index 253b5c7..0ed1f4a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index d12b981..fbd2794 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -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(()) } diff --git a/src/main.rs b/src/main.rs index c065d0b..b202732 100644 --- a/src/main.rs +++ b/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, diff --git a/src/std/std.zr b/src/std/std.zr index a4243ca..42da995 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -228,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) @@ -246,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) @@ -260,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) @@ -402,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") @@ -588,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 @@ -614,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 @@ -661,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 @@ -681,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 @@ -709,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 @@ -799,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