implement our own malloc/free, finally drop libc

This commit is contained in:
2026-03-12 10:02:51 +01:00
parent 3fdf7bb99d
commit 1e7657ea2a
4 changed files with 139 additions and 91 deletions

View File

@@ -7,7 +7,7 @@ A very cool language
* Compiles to x86_64 Assembly
* Growing [standard library](https://github.com/antpiasecki/zern/tree/main/src/std)
* Produces tiny static executables (~30KB with musl)
* ~~No libc required~~ (SOON; still used for memory allocation and DNS resolution)
* No libc required!
* Sometimes works
* Has the pipe operator

View File

@@ -1,70 +0,0 @@
func main[argc: i64, argv: ptr] : i64
if argc < 2
panic("url missing")
let url: str = mem.read64(argv + 8)
if str.len(url) <= 7
panic("missing url scheme")
if !str.equal(str.substr(url, 0, 7), "http://")
panic("invalid url scheme")
let url_len: i64 = str.len(url)
let host_start = 7
let i: i64 = host_start
while i < url_len
if url[i] == '/'
break
i = i + 1
let host: str = str.substr(url, host_start, i - host_start)
let path: str = "/"
if i < url_len
path = str.substr(url, i, url_len - i)
let s: i64 = net.connect(host, 80)
if s < 0
panic("failed to connect")
// very leaky
let req: str = "GET "
req = str.concat(req, path)
req = str.concat(req, " HTTP/1.0\r\nHost: ")
req = str.concat(req, host)
req = str.concat(req, "\r\nConnection: close\r\n\r\n")
net.send(s, req, str.len(req))
mem.free(req)
let header_buf: str = mem.alloc(8192)
let header_size = 0
let found: bool = false
let end_index: i64 = -1
while !found && header_size < 8192
let n: i64 = net.read(s, header_buf + header_size, 8192 - header_size)
if n <= 0
break
let current_size: i64 = header_size + n
i = 0
while i <= current_size - 4
if header_buf[i] == 13 && header_buf[i + 1] == 10 && header_buf[i + 2] == 13 && header_buf[i + 3] == 10
found = true
end_index = i + 4
break
i = i + 1
header_size = current_size
if end_index < header_size
io.print_sized(header_buf + end_index, header_size - end_index)
mem.free(header_buf)
let buffer: ptr = mem.alloc(4096)
while true
let n: i64 = net.read(s, buffer, 4096)
if n <= 0
break
io.print_sized(buffer, n)
mem.free(buffer)
net.close(s)

View File

@@ -106,6 +106,20 @@ impl<'a> CodegenX86_64<'a> {
"section .note.GNU-stack
db 0
section .bss
_heap_head: resq 1
_heap_tail: resq 1
section .text._builtin_heap_head
_builtin_heap_head:
lea rax, [rel _heap_head]
ret
section .text._builtin_heap_tail
_builtin_heap_tail:
lea rax, [rel _heap_tail]
ret
section .text._builtin_read64
_builtin_read64:
mov rax, qword [rdi]

View File

@@ -1,19 +1,128 @@
extern malloc
extern realloc
extern free
extern gethostbyname
func panic[msg: str] : void
io.print("PANIC: ")
io.println(msg)
(0 / 0) // crashes program which is kinda better since you get a gdb backtrace
os.exit(1)
func mem.alloc[x: i64] : ptr
return malloc(x)
const MEM_BLOCK_SIZE = 24
struct mem.Block
size: i64
free: bool
next: mem.Block
func mem._align[x: i64] : i64
return (x + 7) & -8
func mem._split_block[blk: mem.Block, needed: i64] : void
if blk->size >= needed + MEM_BLOCK_SIZE + 8
let new_blk: mem.Block = blk + MEM_BLOCK_SIZE + needed
new_blk->size = blk->size - needed - MEM_BLOCK_SIZE
new_blk->free = true
new_blk->next = blk->next
blk->size = needed
blk->next = new_blk
if mem.read64(_builtin_heap_tail()) == blk
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
return 0
blk->size = size
blk->free = false
blk->next = 0
let tail: mem.Block = mem.read64(_builtin_heap_tail())
if tail
tail->next = blk
mem.write64(_builtin_heap_tail(), blk)
return blk
func mem._coalesce[] : void
let cur: mem.Block = mem.read64(_builtin_heap_head())
while cur && cur->next
let next: mem.Block = cur->next
if cur->free && next->free
cur->size = cur->size + MEM_BLOCK_SIZE + next->size
cur->next = next->next
if !cur->next
mem.write64(_builtin_heap_tail(), cur)
else
cur = cur->next
func mem.alloc[size: i64] : ptr
if size == 0
return 0
size = mem._align(size)
let cur: mem.Block = mem.read64(_builtin_heap_head())
while cur
if cur->free && cur->size >= size
mem._split_block(cur, size)
cur->free = false
return cur + MEM_BLOCK_SIZE
cur = cur->next
let blk: mem.Block = mem._request_space(size)
if !blk
return 0
if !mem.read64(_builtin_heap_head())
mem.write64(_builtin_heap_head(), blk)
return blk + MEM_BLOCK_SIZE
func mem.free[x: ptr] : void
free(x)
if !x
return 0
let blk: mem.Block = x - MEM_BLOCK_SIZE
blk->free = true
mem._coalesce()
func mem.realloc[x: ptr, new_size: i64] : ptr
if !x
return mem.alloc(new_size)
if new_size == 0
mem.free(x)
return 0
new_size = mem._align(new_size)
let blk: mem.Block = x - MEM_BLOCK_SIZE
if blk->size >= new_size
mem._split_block(blk, new_size)
return x
let next: mem.Block = blk->next
if next && next->free
let combined: i64 = blk->size + MEM_BLOCK_SIZE + next->size
if combined >= new_size
blk->size = combined
blk->next = next->next
if !blk->next
mem.write64(_builtin_heap_tail(), blk)
mem._split_block(blk, new_size)
return x
let new_ptr: ptr = mem.alloc(new_size)
if !new_ptr
return 0
for i in 0..blk->size
new_ptr[i] = x[i]
mem.free(x)
return new_ptr
func mem.zero_and_free[x: ptr, size: i64] : void
mem.zero(x, size)
@@ -465,7 +574,7 @@ func array.push[xs: Array, x: i64] : void
let new_capacity = 4
if xs->capacity != 0
new_capacity = xs->capacity * 2
xs->data = realloc(xs->data, new_capacity * 8)
xs->data = mem.realloc(xs->data, new_capacity * 8)
xs->capacity = new_capacity
mem.write64(xs->data + xs->size * 8, x)
@@ -661,13 +770,7 @@ func net.listen[packed_host: i64, port: i64] : i64
return s
func net.connect[host: str, port: i64] : i64
let he: ptr = gethostbyname(host)
if he == 0
return -1
let ip_ptr: ptr = mem.read64(mem.read64(he + 24))
func net.connect[packed_host: i64, port: i64] : i64
let s: i64 = _builtin_syscall(SYS_socket, 2, 1, 0)
if s < 0
return -1
@@ -675,12 +778,13 @@ func net.connect[host: str, port: i64] : i64
let sa: ptr = mem.alloc(16)
mem.zero(sa, 16)
sa[0] = 2
sa[1] = 0
sa[2] = (port >> 8) & 255
sa[3] = port & 255
sa[4] = ip_ptr[0]
sa[5] = ip_ptr[1]
sa[6] = ip_ptr[2]
sa[7] = ip_ptr[3]
sa[4] = (packed_host >> 24) & 255
sa[5] = (packed_host >> 16) & 255
sa[6] = (packed_host >> 8) & 255
sa[7] = packed_host & 255
if _builtin_syscall(SYS_connect, s, sa, 16) < 0
mem.free(sa)