diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 1f8949002..2cf032b73 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2,7 +2,6 @@ package fmt import "core:math/bits" import "core:mem" -import "core:os" import "core:io" import "core:reflect" import "core:runtime" @@ -62,38 +61,6 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist } -fprint :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { - w := io.to_writer(os.stream_from_handle(fd)) - return wprint(w=w, args=args, sep=sep) -} - -fprintln :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { - w := io.to_writer(os.stream_from_handle(fd)) - return wprintln(w=w, args=args, sep=sep) -} -fprintf :: proc(fd: os.Handle, fmt: string, args: ..any) -> int { - w := io.to_writer(os.stream_from_handle(fd)) - return wprintf(w, fmt, ..args) -} -fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info) -> (n: int, err: io.Error) { - w := io.to_writer(os.stream_from_handle(fd)) - return wprint_type(w, info) -} -fprint_typeid :: proc(fd: os.Handle, id: typeid) -> (n: int, err: io.Error) { - w := io.to_writer(os.stream_from_handle(fd)) - return wprint_typeid(w, id) -} - -// print* procedures return the number of bytes written -print :: proc(args: ..any, sep := " ") -> int { return fprint(fd=os.stdout, args=args, sep=sep) } -println :: proc(args: ..any, sep := " ") -> int { return fprintln(fd=os.stdout, args=args, sep=sep) } -printf :: proc(fmt: string, args: ..any) -> int { return fprintf(os.stdout, fmt, ..args) } - -eprint :: proc(args: ..any, sep := " ") -> int { return fprint(fd=os.stderr, args=args, sep=sep) } -eprintln :: proc(args: ..any, sep := " ") -> int { return fprintln(fd=os.stderr, args=args, sep=sep) } -eprintf :: proc(fmt: string, args: ..any) -> int { return fprintf(os.stderr, fmt, ..args) } - - // aprint* procedures return a string that was allocated with the current context // They must be freed accordingly aprint :: proc(args: ..any, sep := " ") -> string { diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin new file mode 100644 index 000000000..b31deb8fc --- /dev/null +++ b/core/fmt/fmt_os.odin @@ -0,0 +1,37 @@ +//+build !freestanding +package fmt + +import "core:runtime" +import "core:os" +import "core:io" + +fprint :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { + w := io.to_writer(os.stream_from_handle(fd)) + return wprint(w=w, args=args, sep=sep) +} + +fprintln :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { + w := io.to_writer(os.stream_from_handle(fd)) + return wprintln(w=w, args=args, sep=sep) +} +fprintf :: proc(fd: os.Handle, fmt: string, args: ..any) -> int { + w := io.to_writer(os.stream_from_handle(fd)) + return wprintf(w, fmt, ..args) +} +fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info) -> (n: int, err: io.Error) { + w := io.to_writer(os.stream_from_handle(fd)) + return wprint_type(w, info) +} +fprint_typeid :: proc(fd: os.Handle, id: typeid) -> (n: int, err: io.Error) { + w := io.to_writer(os.stream_from_handle(fd)) + return wprint_typeid(w, id) +} + +// print* procedures return the number of bytes written +print :: proc(args: ..any, sep := " ") -> int { return fprint(fd=os.stdout, args=args, sep=sep) } +println :: proc(args: ..any, sep := " ") -> int { return fprintln(fd=os.stdout, args=args, sep=sep) } +printf :: proc(fmt: string, args: ..any) -> int { return fprintf(os.stdout, fmt, ..args) } + +eprint :: proc(args: ..any, sep := " ") -> int { return fprint(fd=os.stderr, args=args, sep=sep) } +eprintln :: proc(args: ..any, sep := " ") -> int { return fprintln(fd=os.stderr, args=args, sep=sep) } +eprintf :: proc(fmt: string, args: ..any) -> int { return fprintf(os.stderr, fmt, ..args) } diff --git a/core/os/os_js_wasm32.odin b/core/os/os_js_wasm32.odin deleted file mode 100644 index b70bdda1a..000000000 --- a/core/os/os_js_wasm32.odin +++ /dev/null @@ -1,70 +0,0 @@ -package os - -Handle :: distinct i32; -Errno :: distinct i32; - -ERROR_NONE :: Errno(0); - -O_RDONLY :: 0x00000; -O_WRONLY :: 0x00001; -O_RDWR :: 0x00002; -O_CREATE :: 0x00040; -O_EXCL :: 0x00080; -O_NOCTTY :: 0x00100; -O_TRUNC :: 0x00200; -O_NONBLOCK :: 0x00800; -O_APPEND :: 0x00400; -O_SYNC :: 0x01000; -O_ASYNC :: 0x02000; -O_CLOEXEC :: 0x80000; - -stdout: Handle; -stderr: Handle; -stdin: Handle; - - -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { - return 0, 0; -} -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - return 0, 0; -} -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { - return 0, 0; -} -close :: proc(fd: Handle) -> Errno { - return 0; -} -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { - return 0, 0; -} -current_thread_id :: proc "contextless" () -> int { - return 0; -} - - -file_size :: proc(fd: Handle) -> (i64, Errno) { - return 0, 0; -} - - - -heap_alloc :: proc(size: int) -> rawptr { - return nil; -} -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - if new_size == 0 { - heap_free(ptr); - return nil; - } - if ptr == nil { - return heap_alloc(new_size); - } - - return nil; -} -heap_free :: proc(ptr: rawptr) { - if ptr == nil { - return; - } -} diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin new file mode 100644 index 000000000..d2ba166bd --- /dev/null +++ b/core/os/os_wasi.odin @@ -0,0 +1,97 @@ +package os + +import "core:sys/wasm/wasi" + +Handle :: distinct i32 +Errno :: distinct i32 + +ERROR_NONE :: Errno(wasi.errno_t.SUCCESS) + +O_RDONLY :: 0x00000 +O_WRONLY :: 0x00001 +O_RDWR :: 0x00002 +O_CREATE :: 0x00040 +O_EXCL :: 0x00080 +O_NOCTTY :: 0x00100 +O_TRUNC :: 0x00200 +O_NONBLOCK :: 0x00800 +O_APPEND :: 0x00400 +O_SYNC :: 0x01000 +O_ASYNC :: 0x02000 +O_CLOEXEC :: 0x80000 + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + + +write :: proc(fd: Handle, data: []byte) -> (int, Errno) { + iovs := wasi.ciovec_t(data) + n, err := wasi.fd_write(wasi.fd_t(fd), {iovs}) + return int(n), Errno(err) +} +read :: proc(fd: Handle, data: []byte) -> (int, Errno) { + iovs := wasi.iovec_t(data) + n, err := wasi.fd_read(wasi.fd_t(fd), {iovs}) + return int(n), Errno(err) +} +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { + iovs := wasi.ciovec_t(data) + n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) + return int(n), Errno(err) +} +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { + iovs := wasi.iovec_t(data) + n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) + return int(n), Errno(err) +} +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { + return 0, -1 +} +close :: proc(fd: Handle) -> Errno { + err := wasi.fd_close(wasi.fd_t(fd)) + return Errno(err) +} +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { + n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence)) + return i64(n), Errno(err) +} +current_thread_id :: proc "contextless" () -> int { + return 0 +} + + +file_size :: proc(fd: Handle) -> (i64, Errno) { + stat, err := wasi.fd_filestat_get(wasi.fd_t(fd)) + if err != nil { + return 0, Errno(err) + } + return i64(stat.size), 0 +} + + + +heap_alloc :: proc(size: int) -> rawptr { + return nil +} +heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { + if new_size == 0 { + heap_free(ptr) + return nil + } + if ptr == nil { + return heap_alloc(new_size) + } + + return nil +} +heap_free :: proc(ptr: rawptr) { + if ptr == nil { + return + } +} + + +exit :: proc "contextless" (code: int) -> ! { + wasi.proc_exit(wasi.exitcode_t(code)) +} \ No newline at end of file diff --git a/core/os/stream.odin b/core/os/stream.odin index f80633a3f..5cf5c8405 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -19,7 +19,7 @@ _file_stream_vtable := &io.Stream_VTable{ return }, impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - when ODIN_OS == "windows" { + when ODIN_OS == "windows" || ODIN_OS == "wasi" { fd := Handle(uintptr(s.stream_data)) os_err: Errno n, os_err = read_at(fd, p, offset) @@ -33,7 +33,7 @@ _file_stream_vtable := &io.Stream_VTable{ return }, impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - when ODIN_OS == "windows" { + when ODIN_OS == "windows" || ODIN_OS == "wasi" { fd := Handle(uintptr(s.stream_data)) os_err: Errno n, os_err = write_at(fd, p, offset) diff --git a/core/runtime/default_allocators_general.odin b/core/runtime/default_allocators_general.odin index f25f8f178..6bcfb68ae 100644 --- a/core/runtime/default_allocators_general.odin +++ b/core/runtime/default_allocators_general.odin @@ -1,5 +1,6 @@ //+build !windows //+build !freestanding +//+build !wasi package runtime when ODIN_DEFAULT_TO_NIL_ALLOCATOR { diff --git a/core/runtime/default_allocators_wasi.odin b/core/runtime/default_allocators_wasi.odin new file mode 100644 index 000000000..e2bb7516e --- /dev/null +++ b/core/runtime/default_allocators_wasi.odin @@ -0,0 +1,32 @@ +//+build wasi +package runtime + +default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + switch mode { + case .Alloc: + return nil, .Out_Of_Memory + case .Free: + return nil, .None + case .Free_All: + return nil, .Mode_Not_Implemented + case .Resize: + if size == 0 { + return nil, .None + } + return nil, .Out_Of_Memory + case .Query_Features: + return nil, .Mode_Not_Implemented + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, .None +} + +default_allocator :: proc() -> Allocator { + return Allocator{ + procedure = default_allocator_proc, + data = nil, + } +} diff --git a/core/runtime/default_temporary_allocator.odin b/core/runtime/default_temporary_allocator.odin index 216ed99ff..afe3ee922 100644 --- a/core/runtime/default_temporary_allocator.odin +++ b/core/runtime/default_temporary_allocator.odin @@ -1,191 +1,202 @@ package runtime -@(private) -byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte { - return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)} -} - - DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 1<<22) -Default_Temp_Allocator :: struct { - data: []byte, - curr_offset: int, - prev_allocation: rawptr, - backup_allocator: Allocator, - leaked_allocations: [dynamic][]byte, -} - -default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) { - s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) - s.curr_offset = 0 - s.prev_allocation = nil - s.backup_allocator = backup_allocator - s.leaked_allocations.allocator = backup_allocator -} - -default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) { - if s == nil { - return +when ODIN_OS == "freestanding" { + Default_Temp_Allocator :: struct { } - for ptr in s.leaked_allocations { - free(raw_data(ptr), s.backup_allocator) + + default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) { } - delete(s.leaked_allocations) - delete(s.data, s.backup_allocator) - s^ = {} -} - -@(private) -default_temp_allocator_alloc :: proc(s: ^Default_Temp_Allocator, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - size := size - size = align_forward_int(size, alignment) - - switch { - case s.curr_offset+size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := start + uintptr(s.curr_offset) - ptr = align_forward_uintptr(ptr, uintptr(alignment)) - mem_zero(rawptr(ptr), size) - - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), .None - - case size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := align_forward_uintptr(start, uintptr(alignment)) - mem_zero(rawptr(ptr), size) - - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), .None + + default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) { } - a := s.backup_allocator - if a.procedure == nil { - a = context.allocator - s.backup_allocator = a + + default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + return nil, nil } - - data, err := mem_alloc_bytes(size, alignment, a, loc) - if err != nil { - return data, err +} else { + Default_Temp_Allocator :: struct { + data: []byte, + curr_offset: int, + prev_allocation: rawptr, + backup_allocator: Allocator, + leaked_allocations: [dynamic][]byte, } - if s.leaked_allocations == nil { - s.leaked_allocations = make([dynamic][]byte, a) - } - append(&s.leaked_allocations, data) - - // TODO(bill): Should leaks be notified about? - if logger := context.logger; logger.lowest_level <= .Warning { - if logger.procedure != nil { - logger.procedure(logger.data, .Warning, "default temp allocator resorted to backup_allocator" , logger.options, loc) - } - } - - return data, .None -} - -@(private) -default_temp_allocator_free :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, loc := #caller_location) -> Allocator_Error { - if old_memory == nil { - return .None - } - - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - - if s.prev_allocation == old_memory { - s.curr_offset = int(uintptr(s.prev_allocation) - start) + + default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) { + s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) + s.curr_offset = 0 s.prev_allocation = nil - return .None + s.backup_allocator = backup_allocator + s.leaked_allocations.allocator = backup_allocator } - if start <= old_ptr && old_ptr < end { - // NOTE(bill): Cannot free this pointer but it is valid - return .None + default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) { + if s == nil { + return + } + for ptr in s.leaked_allocations { + free(raw_data(ptr), s.backup_allocator) + } + delete(s.leaked_allocations) + delete(s.data, s.backup_allocator) + s^ = {} } - if len(s.leaked_allocations) != 0 { - for data, i in s.leaked_allocations { - ptr := raw_data(data) - if ptr == old_memory { - free(ptr, s.backup_allocator) - ordered_remove(&s.leaked_allocations, i) - return .None + @(private) + default_temp_allocator_alloc :: proc(s: ^Default_Temp_Allocator, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + size := size + size = align_forward_int(size, alignment) + + switch { + case s.curr_offset+size <= len(s.data): + start := uintptr(raw_data(s.data)) + ptr := start + uintptr(s.curr_offset) + ptr = align_forward_uintptr(ptr, uintptr(alignment)) + mem_zero(rawptr(ptr), size) + + s.prev_allocation = rawptr(ptr) + offset := int(ptr - start) + s.curr_offset = offset + size + return byte_slice(rawptr(ptr), size), .None + + case size <= len(s.data): + start := uintptr(raw_data(s.data)) + ptr := align_forward_uintptr(start, uintptr(alignment)) + mem_zero(rawptr(ptr), size) + + s.prev_allocation = rawptr(ptr) + offset := int(ptr - start) + s.curr_offset = offset + size + return byte_slice(rawptr(ptr), size), .None + } + a := s.backup_allocator + if a.procedure == nil { + a = context.allocator + s.backup_allocator = a + } + + data, err := mem_alloc_bytes(size, alignment, a, loc) + if err != nil { + return data, err + } + if s.leaked_allocations == nil { + s.leaked_allocations = make([dynamic][]byte, a) + } + append(&s.leaked_allocations, data) + + // TODO(bill): Should leaks be notified about? + if logger := context.logger; logger.lowest_level <= .Warning { + if logger.procedure != nil { + logger.procedure(logger.data, .Warning, "default temp allocator resorted to backup_allocator" , logger.options, loc) } } - } - return .Invalid_Pointer - // panic("invalid pointer passed to default_temp_allocator"); -} -@(private) -default_temp_allocator_free_all :: proc(s: ^Default_Temp_Allocator, loc := #caller_location) { - s.curr_offset = 0 - s.prev_allocation = nil - for data in s.leaked_allocations { - free(raw_data(data), s.backup_allocator) - } - clear(&s.leaked_allocations) -} - -@(private) -default_temp_allocator_resize :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, old_size, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - begin := uintptr(raw_data(s.data)) - end := begin + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - if old_memory == s.prev_allocation && old_ptr & uintptr(alignment)-1 == 0 { - if old_ptr+uintptr(size) < end { - s.curr_offset = int(old_ptr-begin)+size - return byte_slice(old_memory, size), .None - } - } - data, err := default_temp_allocator_alloc(s, size, alignment, loc) - if err == .None { - copy(data, byte_slice(old_memory, old_size)) - err = default_temp_allocator_free(s, old_memory, loc) - } - return data, err -} - -default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - - s := (^Default_Temp_Allocator)(allocator_data) - - if s.data == nil { - default_temp_allocator_init(s, DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, default_allocator()) + return data, .None } - switch mode { - case .Alloc: - data, err = default_temp_allocator_alloc(s, size, alignment, loc) - case .Free: - err = default_temp_allocator_free(s, old_memory, loc) - - case .Free_All: - default_temp_allocator_free_all(s, loc) - - case .Resize: - data, err = default_temp_allocator_resize(s, old_memory, old_size, size, alignment, loc) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Free, .Free_All, .Resize, .Query_Features} + @(private) + default_temp_allocator_free :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, loc := #caller_location) -> Allocator_Error { + if old_memory == nil { + return .None } - case .Query_Info: - // Nothing to give + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + old_ptr := uintptr(old_memory) + + if s.prev_allocation == old_memory { + s.curr_offset = int(uintptr(s.prev_allocation) - start) + s.prev_allocation = nil + return .None + } + + if start <= old_ptr && old_ptr < end { + // NOTE(bill): Cannot free this pointer but it is valid + return .None + } + + if len(s.leaked_allocations) != 0 { + for data, i in s.leaked_allocations { + ptr := raw_data(data) + if ptr == old_memory { + free(ptr, s.backup_allocator) + ordered_remove(&s.leaked_allocations, i) + return .None + } + } + } + return .Invalid_Pointer + // panic("invalid pointer passed to default_temp_allocator"); } - return + @(private) + default_temp_allocator_free_all :: proc(s: ^Default_Temp_Allocator, loc := #caller_location) { + s.curr_offset = 0 + s.prev_allocation = nil + for data in s.leaked_allocations { + free(raw_data(data), s.backup_allocator) + } + clear(&s.leaked_allocations) + } + + @(private) + default_temp_allocator_resize :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, old_size, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + begin := uintptr(raw_data(s.data)) + end := begin + uintptr(len(s.data)) + old_ptr := uintptr(old_memory) + if old_memory == s.prev_allocation && old_ptr & uintptr(alignment)-1 == 0 { + if old_ptr+uintptr(size) < end { + s.curr_offset = int(old_ptr-begin)+size + return byte_slice(old_memory, size), .None + } + } + data, err := default_temp_allocator_alloc(s, size, alignment, loc) + if err == .None { + copy(data, byte_slice(old_memory, old_size)) + err = default_temp_allocator_free(s, old_memory, loc) + } + return data, err + } + + default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + + s := (^Default_Temp_Allocator)(allocator_data) + + if s.data == nil { + default_temp_allocator_init(s, DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, default_allocator()) + } + + switch mode { + case .Alloc: + data, err = default_temp_allocator_alloc(s, size, alignment, loc) + case .Free: + err = default_temp_allocator_free(s, old_memory, loc) + + case .Free_All: + default_temp_allocator_free_all(s, loc) + + case .Resize: + data, err = default_temp_allocator_resize(s, old_memory, old_size, size, alignment, loc) + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Free_All, .Resize, .Query_Features} + } + + case .Query_Info: + // Nothing to give + } + + return + } } default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator { @@ -193,4 +204,4 @@ default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator procedure = default_temp_allocator_proc, data = allocator, } -} +} \ No newline at end of file diff --git a/core/runtime/internal.odin b/core/runtime/internal.odin index 4347f28c0..d681e581b 100644 --- a/core/runtime/internal.odin +++ b/core/runtime/internal.odin @@ -2,6 +2,11 @@ package runtime import "core:intrinsics" +@(private) +byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check { + return ([^]byte)(data)[:max(len, 0)] +} + bswap_16 :: proc "contextless" (x: u16) -> u16 { return x>>8 | x<<8 } @@ -833,3 +838,92 @@ fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 { x := i64(a) return i128(x) } + + + + +@(link_name="__umodti3") +umodti3 :: proc "c" (a, b: u128) -> u128 { + r: u128 = --- + _ = udivmod128(a, b, &r) + return r +} + + +@(link_name="__udivmodti4") +udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { + return udivmod128(a, b, rem) +} + +@(link_name="__udivti3") +udivti3 :: proc "c" (a, b: u128) -> u128 { + return udivmodti4(a, b, nil) +} + + +@(link_name="__modti3") +modti3 :: proc "c" (a, b: i128) -> i128 { + s_a := a >> (128 - 1) + s_b := b >> (128 - 1) + an := (a ~ s_a) - s_a + bn := (b ~ s_b) - s_b + + r: u128 = --- + _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) + return (transmute(i128)r ~ s_a) - s_a +} + + +@(link_name="__divmodti4") +divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { + u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) + return transmute(i128)u +} + +@(link_name="__divti3") +divti3 :: proc "c" (a, b: i128) -> i128 { + u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) + return transmute(i128)u +} + + +@(link_name="__fixdfti") +fixdfti :: proc(a: u64) -> i128 { + significandBits :: 52 + typeWidth :: (size_of(u64)*8) + exponentBits :: (typeWidth - significandBits - 1) + maxExponent :: ((1 << exponentBits) - 1) + exponentBias :: (maxExponent >> 1) + + implicitBit :: (u64(1) << significandBits) + significandMask :: (implicitBit - 1) + signBit :: (u64(1) << (significandBits + exponentBits)) + absMask :: (signBit - 1) + exponentMask :: (absMask ~ significandMask) + + // Break a into sign, exponent, significand + aRep := a + aAbs := aRep & absMask + sign := i128(-1 if aRep & signBit != 0 else 1) + exponent := u64((aAbs >> significandBits) - exponentBias) + significand := u64((aAbs & significandMask) | implicitBit) + + // If exponent is negative, the result is zero. + if exponent < 0 { + return 0 + } + + // If the value is too large for the integer type, saturate. + if exponent >= size_of(i128) * 8 { + return max(i128) if sign == 1 else min(i128) + } + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if exponent < significandBits { + return sign * i128(significand >> (significandBits - exponent)) + } else { + return sign * (i128(significand) << (exponent - significandBits)) + } + +} diff --git a/core/runtime/internal_linux.odin b/core/runtime/internal_linux.odin index 359293de3..7ccdf5f69 100644 --- a/core/runtime/internal_linux.odin +++ b/core/runtime/internal_linux.odin @@ -1,89 +1 @@ package runtime - -import "core:intrinsics" - -@(link_name="__umodti3") -umodti3 :: proc "c" (a, b: u128) -> u128 { - r: u128 = --- - _ = udivmod128(a, b, &r) - return r -} - - -@(link_name="__udivmodti4") -udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { - return udivmod128(a, b, rem) -} - -@(link_name="__udivti3") -udivti3 :: proc "c" (a, b: u128) -> u128 { - return udivmodti4(a, b, nil) -} - - -@(link_name="__modti3") -modti3 :: proc "c" (a, b: i128) -> i128 { - s_a := a >> (128 - 1) - s_b := b >> (128 - 1) - an := (a ~ s_a) - s_a - bn := (b ~ s_b) - s_b - - r: u128 = --- - _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) - return (transmute(i128)r ~ s_a) - s_a -} - - -@(link_name="__divmodti4") -divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { - u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) - return transmute(i128)u -} - -@(link_name="__divti3") -divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) - return transmute(i128)u -} - - -@(link_name="__fixdfti") -fixdfti :: proc(a: u64) -> i128 { - significandBits :: 52 - typeWidth :: (size_of(u64)*8) - exponentBits :: (typeWidth - significandBits - 1) - maxExponent :: ((1 << exponentBits) - 1) - exponentBias :: (maxExponent >> 1) - - implicitBit :: (u64(1) << significandBits) - significandMask :: (implicitBit - 1) - signBit :: (u64(1) << (significandBits + exponentBits)) - absMask :: (signBit - 1) - exponentMask :: (absMask ~ significandMask) - - // Break a into sign, exponent, significand - aRep := a - aAbs := aRep & absMask - sign := i128(-1 if aRep & signBit != 0 else 1) - exponent := u64((aAbs >> significandBits) - exponentBias) - significand := u64((aAbs & significandMask) | implicitBit) - - // If exponent is negative, the result is zero. - if exponent < 0 { - return 0 - } - - // If the value is too large for the integer type, saturate. - if exponent >= size_of(i128) * 8 { - return max(i128) if sign == 1 else min(i128) - } - - // If 0 <= exponent < significandBits, right shift to get the result. - // Otherwise, shift left. - if exponent < significandBits { - return sign * i128(significand >> (significandBits - exponent)) - } else { - return sign * (i128(significand) << (exponent - significandBits)) - } - -} diff --git a/core/runtime/internal_windows.odin b/core/runtime/internal_windows.odin index 4b932cd5f..7ccdf5f69 100644 --- a/core/runtime/internal_windows.odin +++ b/core/runtime/internal_windows.odin @@ -1,89 +1 @@ package runtime - -import "core:intrinsics" - -@(link_name="__umodti3") -umodti3 :: proc "c" (a, b: u128) -> u128 { - r: u128 = --- - _ = udivmod128(a, b, &r) - return r -} - - -@(link_name="__udivmodti4") -udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { - return udivmod128(a, b, rem) -} - -@(link_name="__udivti3") -udivti3 :: proc "c" (a, b: u128) -> u128 { - return udivmodti4(a, b, nil) -} - - -@(link_name="__modti3") -modti3 :: proc "c" (a, b: i128) -> i128 { - s_a := a >> (128 - 1) - s_b := b >> (128 - 1) - an := (a ~ s_a) - s_a - bn := (b ~ s_b) - s_b - - r: u128 = --- - _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) - return (transmute(i128)r ~ s_a) - s_a -} - - -@(link_name="__divmodti4") -divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { - u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) - return transmute(i128)u -} - -@(link_name="__divti3") -divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) - return transmute(i128)u -} - - -@(link_name="__fixdfti") -fixdfti :: proc(a: u64) -> i128 { - significandBits :: 52 - typeWidth :: (size_of(u64)*8) - exponentBits :: (typeWidth - significandBits - 1) - maxExponent :: ((1 << exponentBits) - 1) - exponentBias :: (maxExponent >> 1) - - implicitBit :: (u64(1) << significandBits) - significandMask :: (implicitBit - 1) - signBit :: (u64(1) << (significandBits + exponentBits)) - absMask :: (signBit - 1) - exponentMask :: (absMask ~ significandMask) - - // Break a into sign, exponent, significand - aRep := a - aAbs := aRep & absMask - sign := i128(-1 if aRep & signBit != 0 else 1) - exponent := (aAbs >> significandBits) - exponentBias - significand := (aAbs & significandMask) | implicitBit - - // If exponent is negative, the result is zero. - if exponent < 0 { - return 0 - } - - // If the value is too large for the integer type, saturate. - if exponent >= size_of(i128) * 8 { - return max(i128) if sign == 1 else min(i128) - } - - // If 0 <= exponent < significandBits, right shift to get the result. - // Otherwise, shift left. - if exponent < significandBits { - return sign * i128(significand >> (significandBits - exponent)) - } else { - return sign * (i128(significand) << (exponent - significandBits)) - } - -} diff --git a/core/runtime/os_specific_any.odin b/core/runtime/os_specific_any.odin index 397340798..d8608afdb 100644 --- a/core/runtime/os_specific_any.odin +++ b/core/runtime/os_specific_any.odin @@ -1,5 +1,4 @@ -//+build !freestanding -//+build !windows +//+build !freestanding !wasi !windows package runtime import "core:os" diff --git a/core/runtime/os_specific_wasi.odin b/core/runtime/os_specific_wasi.odin new file mode 100644 index 000000000..3f69504ee --- /dev/null +++ b/core/runtime/os_specific_wasi.odin @@ -0,0 +1,10 @@ +//+build wasi +package runtime + +import "core:sys/wasm/wasi" + +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + data := (wasi.ciovec_t)(data) + n, err := wasi.fd_write(1, {data}) + return int(n), _OS_Errno(err) +} diff --git a/core/runtime/procs.odin b/core/runtime/procs.odin index d97a3fadf..09ad25379 100644 --- a/core/runtime/procs.odin +++ b/core/runtime/procs.odin @@ -1,12 +1,39 @@ package runtime -memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { - if ptr != nil && len != 0 { - b := byte(val) - p := ([^]byte)(ptr) - for i in 0.. rawptr { + if ptr != nil && len != 0 { + b := byte(val) + p := ([^]byte)(ptr) + for i in 0.. rawptr { + if dst != src { + d, s := ([^]byte)(dst), ([^]byte)(src) + d_end, s_end := d[len:], s[len:] + for i := len-1; i >= 0; i -= 1 { + d[i] = s[i] + } + } + return dst + + } +} else { + memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + if ptr != nil && len != 0 { + b := byte(val) + p := ([^]byte)(ptr) + for i in 0.. i64 { + return a +} \ No newline at end of file diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin new file mode 100644 index 000000000..2e2a99617 --- /dev/null +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -0,0 +1,1899 @@ +//+build wasm32 +package sys_wasi + +foreign import wasi "wasi_snapshot_preview1" + +DIRCOOKIE_START :: u64(0) +size_t :: uint + +filesize_t :: distinct u64 +timestamp_t :: distinct u64 + +clockid_t :: distinct u32 +CLOCK_MONOTONIC :: clockid_t(0) +CLOCK_PROCESS_CPUTIME_ID :: clockid_t(1) +CLOCK_REALTIME :: clockid_t(2) +CLOCK_THREAD_CPUTIME_ID :: clockid_t(3) + +errno_t :: enum u16 { + // No error occurred. System call completed successfully. + SUCCESS = 0, + // Argument list too long. + TOOBIG = 1, + // Permission denied. + ACCESS = 2, + // Address in use. + ADDRINUSE = 3, + // Address not available. + ADDRNOTAVAIL = 4, + // Address family not supported. + AFNOSUPPORT = 5, + // Resource unavailable, or operation would block. + AGAIN = 6, + // Connection already in progress. + ALREADY = 7, + // Bad file descriptor. + BADF = 8, + // Bad message. + BADMSG = 9, + // Device or resource busy. + BUSY = 10, + // Operation canceled. + CANCELED = 11, + // No child processes. + CHILD = 12, + // Connection aborted. + CONNABORTED = 13, + // Connection refused. + CONNREFUSED = 14, + // Connection reset. + CONNRESET = 15, + // Resource deadlock would occur. + DEADLK = 16, + // Destination address required. + DESTADDRREQ = 17, + // Mathematics argument out of domain of function. + DOM = 18, + // Reserved. + DQUOT = 19, + // File exists. + EXIST = 20, + // Bad address. + FAULT = 21, + // File too large. + FBIG = 22, + // Host is unreachable. + HOSTUNREACH = 23, + // Identifier removed. + IDRM = 24, + // Illegal byte sequence. + ILSEQ = 25, + // Operation in progress. + INPROGRESS = 26, + // Interrupted function. + INTR = 27, + // Invalid argument. + INVAL = 28, + // I/O error. + IO = 29, + // Socket is connected. + ISCONN = 30, + // Is a directory. + ISDIR = 31, + // Too many levels of symbolic links. + LOOP = 32, + // File descriptor value too large. + MFILE = 33, + // Too many links. + MLINK = 34, + // Message too large. + MSGSIZE = 35, + // Reserved. + MULTIHOP = 36, + // Filename too long. + NAMETOOLONG = 37, + // Network is down. + NETDOWN = 38, + // Connection aborted by network. + NETRESET = 39, + // Network unreachable. + NETUNREACH = 40, + // Too many files open in system. + NFILE = 41, + // No buffer space available. + NOBUFS = 42, + // No such device. + NODEV = 43, + // No such file or directory. + NOENT = 44, + // Executable file format error. + NOEXEC = 45, + // No locks available. + NOLCK = 46, + // Reserved. + NOLINK = 47, + // Not enough space. + NOMEM = 48, + // No message of the desired type. + NOMSG = 49, + // Protocol not available. + NOPROTOOPT = 50, + // No space left on device. + NOSPC = 51, + // Function not supported. + NOSYS = 52, + // The socket is not connected. + NOTCONN = 53, + // Not a directory or a symbolic link to a directory. + NOTDIR = 54, + // Directory not empty. + NOTEMPTY = 55, + // State not recoverable. + NOTRECOVERABLE = 56, + // Not a socket. + NOTSOCK = 57, + // Not supported, or operation not supported on socket. + NOTSUP = 58, + // Inappropriate I/O control operation. + NOTTY = 59, + // No such device or address. + NXIO = 60, + // Value too large to be stored in data type. + OVERFLOW = 61, + // Previous owner died. + OWNERDEAD = 62, + // Operation not permitted. + PERM = 63, + // Broken pipe. + PIPE = 64, + // Protocol error. + PROTO = 65, + // Protocol not supported. + PROTONOSUPPORT = 66, + // Protocol wrong type for socket. + PROTOTYPE = 67, + // Result too large. + RANGE = 68, + // Read-only file system. + ROFS = 69, + // Invalid seek. + SPIPE = 70, + // No such process. + SRCH = 71, + // Reserved. + STALE = 72, + // Connection timed out. + TIMEDOUT = 73, + // Text file busy. + TXTBSY = 74, + // Cross-device link. + XDEV = 75, + // Extension: Capabilities insufficient. + NOTCAPABLE = 76, +} + + +rights_t :: distinct bit_set[rights_flag_t; u64] +rights_flag_t :: enum u64 { + /** + * The right to invoke `fd_datasync`. + * If `path_open` is set, includes the right to invoke + * `path_open` with `fdflags::dsync`. + */ + FD_DATASYNC = 0, + + /** + * The right to invoke `fd_read` and `sock_recv`. + * If `rights::fd_seek` is set, includes the right to invoke `fd_pread`. + */ + FD_READ = 1, + + /** + * The right to invoke `fd_seek`. This flag implies `rights::fd_tell`. + */ + FD_SEEK = 2, + + /** + * The right to invoke `fd_fdstat_set_flags`. + */ + FD_FDSTAT_SET_FLAGS = 3, + + /** + * The right to invoke `fd_sync`. + * If `path_open` is set, includes the right to invoke + * `path_open` with `fdflags::rsync` and `fdflags::dsync`. + */ + FD_SYNC = 4, + + /** + * The right to invoke `fd_seek` in such a way that the file offset + * remains unaltered (i.e., `whence::cur` with offset zero), or to + * invoke `fd_tell`. + */ + FD_TELL = 5, + + /** + * The right to invoke `fd_write` and `sock_send`. + * If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`. + */ + FD_WRITE = 6, + + /** + * The right to invoke `fd_advise`. + */ + FD_ADVISE = 7, + + /** + * The right to invoke `fd_allocate`. + */ + FD_ALLOCATE = 8, + + /** + * The right to invoke `path_create_directory`. + */ + PATH_CREATE_DIRECTORY = 9, + + /** + * If `path_open` is set, the right to invoke `path_open` with `oflags::creat`. + */ + PATH_CREATE_FILE = 10, + + /** + * The right to invoke `path_link` with the file descriptor as the + * source directory. + */ + PATH_LINK_SOURCE = 11, + + /** + * The right to invoke `path_link` with the file descriptor as the + * target directory. + */ + PATH_LINK_TARGET = 12, + + /** + * The right to invoke `path_open`. + */ + PATH_OPEN = 13, + + /** + * The right to invoke `fd_readdir`. + */ + FD_READDIR = 14, + + /** + * The right to invoke `path_readlink`. + */ + PATH_READLINK = 15, + + /** + * The right to invoke `path_rename` with the file descriptor as the source directory. + */ + PATH_RENAME_SOURCE = 16, + + /** + * The right to invoke `path_rename` with the file descriptor as the target directory. + */ + PATH_RENAME_TARGET = 17, + + /** + * The right to invoke `path_filestat_get`. + */ + PATH_FILESTAT_GET = 18, + + /** + * The right to change a file's size (there is no `path_filestat_set_size`). + * If `path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`. + */ + PATH_FILESTAT_SET_SIZE = 19, + + /** + * The right to invoke `path_filestat_set_times`. + */ + PATH_FILESTAT_SET_TIMES = 20, + + /** + * The right to invoke `fd_filestat_get`. + */ + FD_FILESTAT_GET = 21, + + /** + * The right to invoke `fd_filestat_set_size`. + */ + FD_FILESTAT_SET_SIZE = 22, + + /** + * The right to invoke `fd_filestat_set_times`. + */ + FD_FILESTAT_SET_TIMES = 23, + + /** + * The right to invoke `path_symlink`. + */ + PATH_SYMLINK = 24, + + /** + * The right to invoke `path_remove_directory`. + */ + PATH_REMOVE_DIRECTORY = 25, + + /** + * The right to invoke `path_unlink_file`. + */ + PATH_UNLINK_FILE = 26, + + /** + * If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`. + * If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`. + */ + POLL_FD_READWRITE = 27, + + /** + * The right to invoke `sock_shutdown`. + */ + SOCK_SHUTDOWN = 28, +} + +fd_t :: distinct i32 + +// iovec_t :: struct { +// buf: [^]u8, +// buf_len: size_t, +// } +// ciovec_t :: struct { +// buf: [^]u8, +// buf_len: size_t, +// } +iovec_t :: []byte +ciovec_t :: []byte + + +filedelta_t :: distinct i64 + +whence_t :: enum u8 { + SET = 0, + CUR = 1, + END = 2, +} + +dircookie_t :: distinct u64 +dirnamlen_t :: distinct u32 +inode_t :: distinct u64 + +filetype_t :: enum u8 { + /** + * The type of the file descriptor or file is unknown or is different from any of the other types specified. + */ + UNKNOWN = 0, + + /** + * The file descriptor or file refers to a block device inode. + */ + BLOCK_DEVICE = 1, + + /** + * The file descriptor or file refers to a character device inode. + */ + CHARACTER_DEVICE = 2, + + /** + * The file descriptor or file refers to a directory inode. + */ + DIRECTORY = 3, + + /** + * The file descriptor or file refers to a regular file inode. + */ + REGULAR_FILE = 4, + + /** + * The file descriptor or file refers to a datagram socket. + */ + SOCKET_DGRAM = 5, + + /** + * The file descriptor or file refers to a byte-stream socket. + */ + SOCKET_STREAM = 6, + + /** + * The file refers to a symbolic link inode. + */ + SYMBOLIC_LINK = 7, +} + +dirent_t :: struct { + d_next: dircookie_t, + d_ino: inode_t, + d_namlen: dirnamlen_t, + d_type: filetype_t, +} + +advice_t :: enum u8 { + /** + * The application has no advice to give on its behavior with respect to the specified data. + */ + NORMAL = 0, + + /** + * The application expects to access the specified data sequentially from lower offsets to higher offsets. + */ + SEQUENTIAL = 1, + + /** + * The application expects to access the specified data in a random order. + */ + RANDOM = 2, + + /** + * The application expects to access the specified data in the near future. + */ + WILLNEED = 3, + + /** + * The application expects that it will not access the specified data in the near future. + */ + DONTNEED = 4, + + /** + * The application expects to access the specified data once and then not reuse it thereafter. + */ + NOREUSE = 5, +} + +fdflags_t :: distinct bit_set[fdflag_t; u16] +fdflag_t :: enum u16 { + /** + * Append mode: Data written to the file is always appended to the file's end. + */ + APPEND = 0, + + /** + * Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. + */ + DSYNC = 1, + + /** + * Non-blocking mode. + */ + NONBLOCK = 2, + + /** + * Synchronized read I/O operations. + */ + RSYNC = 3, + + /** + * Write according to synchronized I/O file integrity completion. In + * addition to synchronizing the data stored in the file, the implementation + * may also synchronously update the file's metadata. + */ + SYNC = 4, +} + +fdstat_t :: struct { + /** + * File type. + */ + fs_filetype: filetype_t, + + /** + * File descriptor flags. + */ + fs_flags: fdflags_t, + + /** + * Rights that apply to this file descriptor. + */ + fs_rights_base: rights_t, + + /** + * Maximum set of rights that may be installed on new file descriptors that + * are created through this file descriptor, e.g., through `path_open`. + */ + fs_rights_inheriting: rights_t, +} + +device_t :: distinct u64 + + +fstflags_t :: distinct bit_set[fstflag_t; u16] +fstflag_t :: enum u16 { + /** + * Adjust the last data access timestamp to the value stored in `filestat::atim`. + */ + ATIM = 0, + + /** + * Adjust the last data access timestamp to the time of clock `clockid::realtime`. + */ + ATIM_NOW = 1, + + /** + * Adjust the last data modification timestamp to the value stored in `filestat::mtim`. + */ + MTIM = 2, + + /** + * Adjust the last data modification timestamp to the time of clock `clockid::realtime`. + */ + MTIM_NOW = 3, + +} + +lookupflags_t :: distinct bit_set[lookupflag_t; u32] +lookupflag_t :: enum u32 { + /** + * As long as the resolved path corresponds to a symbolic link, it is expanded. + */ + SYMLINK_FOLLOW = 0, +} + +oflags_t :: distinct bit_set[oflag_t; u16] +oflag_t :: enum u16 { + /** + * Create file if it does not exist. + */ + CREATE = 0, + + /** + * Fail if not a directory. + */ + DIRECTORY = 1, + + /** + * Fail if file already exists. + */ + EXCL = 2, + + /** + * Truncate file to size 0. + */ + TRUNC = 3, +} + +linkcount_t :: distinct u64 + +filestat_t :: struct { + /** + * Device ID of device containing the file. + */ + dev: device_t, + + /** + * File serial number. + */ + ino: inode_t, + + /** + * File type. + */ + filetype: filetype_t, + + /** + * Number of hard links to the file. + */ + nlink: linkcount_t, + + /** + * For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. + */ + size: filesize_t, + + /** + * Last data access timestamp. + */ + atim: timestamp_t, + + /** + * Last data modification timestamp. + */ + mtim: timestamp_t, + + /** + * Last file status change timestamp. + */ + ctim: timestamp_t, +} + +userdata_t :: distinct u64 + +eventtype_t :: enum u8 { + /** + * The time value of clock `subscription_clock::id` has + * reached timestamp `subscription_clock::timeout`. + */ + CLOCK = 0, + + /** + * File descriptor `subscription_fd_readwrite::file_descriptor` has data + * available for reading. This event always triggers for regular files. + */ + FD_READ = 1, + + /** + * File descriptor `subscription_fd_readwrite::file_descriptor` has capacity + * available for writing. This event always triggers for regular files. + */ + FD_WRITE = 2, +} + +eventrwflags_t :: distinct bit_set[eventrwflag_t; u16] +eventrwflag_t :: enum u16 { + /** + * The peer of this socket has closed or disconnected. + */ + FD_READWRITE_HANGUP = 0, +} + +event_fd_readwrite_t :: struct { + /** + * The number of bytes available for reading or writing. + */ + nbytes: filesize_t, + + /** + * The state of the file descriptor. + */ + flags: eventrwflags_t, +} + +event_t :: struct { + /** + * User-provided value that got attached to `subscription::userdata`. + */ + userdata: userdata_t, + + /** + * If non-zero, an error that occurred while processing the subscription request. + */ + error: errno_t, + + /** + * The type of event that occured + */ + type: eventtype_t, + + /** + * The contents of the event, if it is an `eventtype::fd_read` or + * `eventtype::fd_write`. `eventtype::clock` events ignore this field. + */ + fd_readwrite: event_fd_readwrite_t, +} + +subclockflags_t :: distinct bit_set[subclockflag_t; u16] +subclockflag_t :: enum u16 { + /** + * If set, treat the timestamp provided in + * `subscription_clock::timeout` as an absolute timestamp of clock + * `subscription_clock::id`. If clear, treat the timestamp + * provided in `subscription_clock::timeout` relative to the + * current time value of clock `subscription_clock::id`. + */ + SUBSCRIPTION_CLOCK_ABSTIME = 0, + +} + +subscription_clock_t :: struct { + /** + * The clock against which to compare the timestamp. + */ + id: clockid_t, + + /** + * The absolute or relative timestamp. + */ + timeout: timestamp_t, + + /** + * The amount of time that the implementation may wait additionally + * to coalesce with other events. + */ + precision: timestamp_t, + + /** + * Flags specifying whether the timeout is absolute or relative + */ + flags: subclockflags_t, +} + +subscription_fd_readwrite_t :: struct { + /** + * The file descriptor on which to wait for it to become ready for reading or writing. + */ + file_descriptor: fd_t, +} + +subscription_t :: struct { + /** + * User-provided value that is attached to the subscription in the + * implementation and returned through `event::userdata`. + */ + userdata: userdata_t, + + /** + * The type of the event to which to subscribe, and its contents + */ + using contents: struct { + tag: u8, + using u: struct #raw_union { + clock: subscription_clock_t, + fd_read: subscription_fd_readwrite_t, + fd_write: subscription_fd_readwrite_t, + }, + }, +} + +exitcode_t :: distinct u32 + +signal_t :: enum u8 { + /** + * No signal. Note that POSIX has special semantics for `kill(pid, 0)`, + * so this value is reserved. + */ + NONE = 0, + + /** + * Hangup. + * Action: Terminates the process. + */ + HUP = 1, + + /** + * Terminate interrupt signal. + * Action: Terminates the process. + */ + INT = 2, + + /** + * Terminal quit signal. + * Action: Terminates the process. + */ + QUIT = 3, + + /** + * Illegal instruction. + * Action: Terminates the process. + */ + ILL = 4, + + /** + * Trace/breakpoint trap. + * Action: Terminates the process. + */ + TRAP = 5, + + /** + * Process abort signal. + * Action: Terminates the process. + */ + ABRT = 6, + + /** + * Access to an undefined portion of a memory object. + * Action: Terminates the process. + */ + BUS = 7, + + /** + * Erroneous arithmetic operation. + * Action: Terminates the process. + */ + FPE = 8, + + /** + * Kill. + * Action: Terminates the process. + */ + KILL = 9, + + /** + * User-defined signal 1. + * Action: Terminates the process. + */ + USR1 = 10, + + /** + * Invalid memory reference. + * Action: Terminates the process. + */ + SEGV = 11, + + /** + * User-defined signal 2. + * Action: Terminates the process. + */ + USR2 = 12, + + /** + * Write on a pipe with no one to read it. + * Action: Ignored. + */ + PIPE = 13, + + /** + * Alarm clock. + * Action: Terminates the process. + */ + ALRM = 14, + + /** + * Termination signal. + * Action: Terminates the process. + */ + TERM = 15, + + /** + * Child process terminated, stopped, or continued. + * Action: Ignored. + */ + CHLD = 16, + + /** + * Continue executing, if stopped. + * Action: Continues executing, if stopped. + */ + CONT = 17, + + /** + * Stop executing. + * Action: Stops executing. + */ + STOP = 18, + + /** + * Terminal stop signal. + * Action: Stops executing. + */ + TSTP = 19, + + /** + * Background process attempting read. + * Action: Stops executing. + */ + TTIN = 20, + + /** + * Background process attempting write. + * Action: Stops executing. + */ + TTOU = 21, + + /** + * High bandwidth data is available at a socket. + * Action: Ignored. + */ + URG = 22, + + /** + * CPU time limit exceeded. + * Action: Terminates the process. + */ + XCPU = 23, + + /** + * File size limit exceeded. + * Action: Terminates the process. + */ + XFSZ = 24, + + /** + * Virtual timer expired. + * Action: Terminates the process. + */ + VTALRM = 25, + + /** + * Profiling timer expired. + * Action: Terminates the process. + */ + PROF = 26, + + /** + * Window changed. + * Action: Ignored. + */ + WINCH = 27, + + /** + * I/O possible. + * Action: Terminates the process. + */ + POLL = 28, + + /** + * Power failure. + * Action: Terminates the process. + */ + PWR = 29, + + /** + * Bad system call. + * Action: Terminates the process. + */ + SYS = 30, +} + + +riflags_t :: distinct bit_set[riflag_t; u16] +riflag_t :: enum u16 { + /** + * Returns the message without removing it from the socket's receive queue. + */ + RECV_PEEK = 0, + + /** + * On byte-stream sockets, block until the full amount of data can be returned. + */ + RECV_WAITALL = 1, +} + +roflags_t :: distinct bit_set[roflag_t; u16] +roflag_t :: enum u16 { + /** + * Returned by `sock_recv`: Message data has been truncated. + */ + RECV_DATA_TRUNCATED = 0, +} + +siflags_t :: distinct bit_set[siflag_t; u16] +siflag_t :: enum u16 { +} + + +sdflags_t :: distinct bit_set[sdflag_t; u8] +sdflag_t :: enum u8 { + /** + * Disables further receive operations. + */ + RD = 0, + + /** + * Disables further send operations. + */ + WR = 1, +} + +preopentype_t :: enum u8 { + DIR = 0, +} + +prestat_dir_t :: struct { + pr_name_len: size_t, +} + +prestat_t :: struct { + tag: u8, + using u: struct { + dir: prestat_dir_t, + }, +} + +@(default_calling_convention="c") +foreign wasi { + /** + * Read command-line argument data. + * The size of the array should match that returned by `args_sizes_get` + */ + args_get :: proc( + argv: [^]cstring, + argv_buf: [^]byte, + ) -> errno_t --- + /** + * Read environment variable data. + * The sizes of the buffers should match that returned by `environ_sizes_get`. + */ + environ_get :: proc( + environ: [^]cstring, + environ_buf: [^]byte, + ) -> errno_t --- + /** + * Provide file advisory information on a file descriptor. + * Note: This is similar to `posix_fadvise` in POSIX. + */ + fd_advise :: proc( + fd: fd_t, + /** + * The offset within the file to which the advisory applies. + */ + offset: filesize_t, + /** + * The length of the region to which the advisory applies. + */ + len: filesize_t, + /** + * The advice. + */ + advice: advice_t, + ) -> errno_t --- + /** + * Force the allocation of space in a file. + * Note: This is similar to `posix_fallocate` in POSIX. + */ + fd_allocate :: proc( + fd: fd_t, + /** + * The offset at which to start the allocation. + */ + offset: filesize_t, + /** + * The length of the area that is allocated. + */ + len: filesize_t, + ) -> errno_t --- + /** + * Close a file descriptor. + * Note: This is similar to `close` in POSIX. + */ + fd_close :: proc( + fd: fd_t, + ) -> errno_t --- + /** + * Synchronize the data of a file to disk. + * Note: This is similar to `fdatasync` in POSIX. + */ + fd_datasync :: proc( + fd: fd_t, + ) -> errno_t --- + /** + * Adjust the flags associated with a file descriptor. + * Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. + */ + fd_fdstat_set_flags :: proc( + fd: fd_t, + /** + * The desired values of the file descriptor flags. + */ + flags: fdflags_t, + ) -> errno_t --- + /** + * Adjust the rights associated with a file descriptor. + * This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights + */ + fd_fdstat_set_rights :: proc( + fd: fd_t, + /** + * The desired rights of the file descriptor. + */ + fs_rights_base: rights_t, + fs_rights_inheritin: rights_t, + ) -> errno_t --- + /** + * Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. + * Note: This is similar to `ftruncate` in POSIX. + */ + fd_filestat_set_size :: proc( + fd: fd_t, + /** + * The desired file size. + */ + size: filesize_t, + ) -> errno_t --- + /** + * Adjust the timestamps of an open file or directory. + * Note: This is similar to `futimens` in POSIX. + */ + fd_filestat_set_times :: proc( + fd: fd_t, + /** + * The desired values of the data access timestamp. + */ + atim: timestamp_t, + /** + * The desired values of the data modification timestamp. + */ + mtim: timestamp_t, + /** + * A bitmask indicating which timestamps to adjust. + */ + fst_flags: fstflags_t, + ) -> errno_t --- + /** + * Atomically replace a file descriptor by renumbering another file descriptor. + * Due to the strong focus on thread safety, this environment does not provide + * a mechanism to duplicate or renumber a file descriptor to an arbitrary + * number, like `dup2()`. This would be prone to race conditions, as an actual + * file descriptor with the same number could be allocated by a different + * thread at the same time. + * This function provides a way to atomically renumber file descriptors, which + * would disappear if `dup2()` were to be removed entirely. + */ + fd_renumber :: proc( + fd: fd_t, + /** + * The file descriptor to overwrite. + */ + to: fd_t, + ) -> errno_t --- + /** + * Synchronize the data and metadata of a file to disk. + * Note: This is similar to `fsync` in POSIX. + */ + fd_sync :: proc( + f: fd_t, + ) -> errno_t --- + /** + * Terminate the process normally. An exit code of 0 indicates successful + * termination of the program. The meanings of other values is dependent on + * the environment. + */ + proc_exit :: proc( + /** + * The exit code returned by the process. + */ + rval: exitcode_t, + ) -> ! --- + /** + * Send a signal to the process of the calling thread. + * Note: This is similar to `raise` in POSIX. + */ + proc_raise :: proc( + /** + * The signal condition to trigger. + */ + sig: signal_t, + ) -> errno_t --- + /** + * Temporarily yield execution of the calling thread. + * Note: This is similar to `sched_yield` in POSIX. + */ + sched_yield :: proc() -> errno_t --- + /** + * Shut down socket send and receive channels. + * Note: This is similar to `shutdown` in POSIX. + */ + sock_shutdown :: proc( + fd: fd_t, + /** + * Which channels on the socket to shut down. + */ + how: sdflags_t, + ) -> errno_t --- +} + +/** + * Return command-line argument data sizes. + * @return + * Returns the number of arguments and the size of the argument string + * data, or an error. + */ +args_sizes_get :: proc "c" () -> (num_args, size_of_args: size_t, err: errno_t) { + err = wasi_args_sizes_get(&num_args, &size_of_args) + return +} +/** + * Return environment variable data sizes. + * @return + * Returns the number of environment variable arguments and the size of the + * environment variable data. + */ +environ_sizes_get :: proc "c" () -> (num_envs, size_of_envs: size_t, err: errno_t) { + err = wasi_environ_sizes_get(&num_envs, &size_of_envs) + return +} +/** + * Return the resolution of a clock. + * Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, + * return `errno::inval`. + * Note: This is similar to `clock_getres` in POSIX. + * @return + * The resolution of the clock, or an error if one happened. + */ +clock_res_get :: proc "c" ( + /** + * The clock for which to return the resolution. + */ + id: clockid_t, +) -> (ts: timestamp_t, err: errno_t) { + err = wasi_clock_res_get(id, &ts) + return +} +/** + * Return the time value of a clock. + * Note: This is similar to `clock_gettime` in POSIX. + * @return + * The time value of the clock. + */ +clock_time_get :: proc "c" ( + /** + * The clock for which to return the time. + */ + id: clockid_t, + /** + * The maximum lag (exclusive) that the returned time value may have, compared to its actual value. + */ + precision: timestamp_t, +) -> (ts: timestamp_t, err: errno_t) { + err = wasi_clock_time_get(id, precision, &ts) + return +} +/** + * Get the attributes of a file descriptor. + * Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. + * @return + * The buffer where the file descriptor's attributes are stored. + */ +fd_fdstat_get :: proc "c" ( + fd: fd_t, +) -> (stat: fdstat_t, err: errno_t) { + err = wasi_fd_fdstat_get(fd, &stat) + return +} +/** + * Return the attributes of an open file. + * @return + * The buffer where the file's attributes are stored. + */ +fd_filestat_get :: proc "c" ( + fd: fd_t, +) -> (stat: filestat_t, err: errno_t) { + err = wasi_fd_filestat_get(fd, &stat) + return +} + + + + +/** + * Read from a file descriptor, without using and updating the file descriptor's offset. + * Note: This is similar to `preadv` in POSIX. + * @return + * The number of bytes read. + */ +fd_pread :: proc "c" ( + fd: fd_t, + /** + * List of scatter/gather vectors in which to store data. + */ + iovs: []iovec_t, + /** + * The offset within the file at which to read. + */ + offset: filesize_t, +) -> (n: size_t, err: errno_t) { + err = wasi_fd_pread(fd, raw_data(iovs), len(iovs), offset, &n) + return +} +/** + * Return a description of the given preopened file descriptor. + * @return + * The buffer where the description is stored. + */ +fd_prestat_get :: proc "c" ( + fd: fd_t, +) -> (desc: prestat_t, err: errno_t) { + err = wasi_fd_prestat_get(fd, &desc) + return +} +/** + * Write to a file descriptor, without using and updating the file descriptor's offset. + * Note: This is similar to `pwritev` in POSIX. + * @return + * The number of bytes written. + */ +fd_pwrite :: proc "c" ( + fd: fd_t, + /** + * List of scatter/gather vectors from which to retrieve data. + */ + iovs: []ciovec_t, + /** + * The offset within the file at which to write. + */ + offset: filesize_t, +) -> (n: size_t, err: errno_t) { + err = wasi_fd_pwrite(fd, raw_data(iovs), len(iovs), offset, &n) + return +} +/** + * Read from a file descriptor. + * Note: This is similar to `readv` in POSIX. + * @return + * The number of bytes read. + */ +fd_read :: proc "c" ( + fd: fd_t, + /** + * List of scatter/gather vectors to which to store data. + */ + iovs: []iovec_t, +) -> (n: size_t, err: errno_t) { + err = wasi_fd_read(fd, raw_data(iovs), len(iovs), &n) + return +} +/** + * Read directory entries from a directory. + * When successful, the contents of the output buffer consist of a sequence of + * directory entries. Each directory entry consists of a `dirent` object, + * followed by `dirent::d_namlen` bytes holding the name of the directory + * entry. + * This function fills the output buffer as much as possible, potentially + * truncating the last directory entry. This allows the caller to grow its + * read buffer size in case it's too small to fit a single large directory + * entry, or skip the oversized directory entry. + * @return + * The number of bytes stored in the read buffer. If less than the size of the read buffer, the end of the directory has been reached. + */ +fd_readdir :: proc "c" ( + fd: fd_t, + /** + * The buffer where directory entries are stored + */ + buf: []byte, + /** + * The location within the directory to start reading + */ + cookie: dircookie_t, +) -> (n: size_t, err: errno_t) { + err = wasi_fd_readdir(fd, raw_data(buf), len(buf), cookie, &n) + return +} +/** + * Move the offset of a file descriptor. + * Note: This is similar to `lseek` in POSIX. + * @return + * The new offset of the file descriptor, relative to the start of the file. + */ +fd_seek :: proc "c" ( + fd: fd_t, + /** + * The number of bytes to move. + */ + offset: filedelta_t, + /** + * The base from which the offset is relative. + */ + whence: whence_t, +) -> (new_offset: filesize_t, err: errno_t) { + err = wasi_fd_seek(fd, offset, whence, &new_offset) + return +} +/** + * Return the current offset of a file descriptor. + * Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. + * @return + * The current offset of the file descriptor, relative to the start of the file. + */ +fd_tell :: proc "c" ( + fd: fd_t, +) -> (offset: filesize_t, err: errno_t) { + err = wasi_fd_tell(fd, &offset) + return +} +/** + * Write to a file descriptor. + * Note: This is similar to `writev` in POSIX. + */ +fd_write :: proc "c" ( + fd: fd_t, + /** + * List of scatter/gather vectors from which to retrieve data. + */ + iovs: []ciovec_t, +) -> (n: size_t, err: errno_t) { + err = wasi_fd_write(fd, raw_data(iovs), len(iovs), &n) + return +} +/** + * Return the attributes of a file or directory. + * Note: This is similar to `stat` in POSIX. + * @return + * The buffer where the file's attributes are stored. + */ +path_filestat_get :: proc "c" ( + fd: fd_t, + /** + * Flags determining the method of how the path is resolved. + */ + flags: lookupflags_t, + /** + * The path of the file or directory to inspect. + */ + path: string, +) -> (offset: filestat_t, err: errno_t) { + err = wasi_path_filestat_get(fd, flags, raw_data(path), len(path), &offset) + return +} +/** + * Open a file or directory. + * The returned file descriptor is not guaranteed to be the lowest-numbered + * file descriptor not currently open; it is randomized to prevent + * applications from depending on making assumptions about indexes, since this + * is error-prone in multi-threaded contexts. The returned file descriptor is + * guaranteed to be less than 2**31. + * Note: This is similar to `openat` in POSIX. + * @return + * The file descriptor of the file that has been opened. + */ +path_open :: proc "c" ( + fd: fd_t, + /** + * Flags determining the method of how the path is resolved. + */ + dirflags: lookupflags_t, + /** + * The relative path of the file or directory to open, relative to the + * `path_open::fd` directory. + */ + path: string, + /** + * The method by which to open the file. + */ + oflags: oflags_t, + /** + * The initial rights of the newly created file descriptor. The + * implementation is allowed to return a file descriptor with fewer rights + * than specified, if and only if those rights do not apply to the type of + * file being opened. + * The *base* rights are rights that will apply to operations using the file + * descriptor itself, while the *inheriting* rights are rights that apply to + * file descriptors derived from it. + */ + fs_rights_base: rights_t, + fs_rights_inheriting: rights_t, + fdflags: fdflags_t, +) -> (file: fd_t, err: errno_t) { + err = wasi_path_open(fd, dirflags, raw_data(path), len(path), oflags, fs_rights_base, fs_rights_inheriting, fdflags, &file) + return +} +/** + * Read the contents of a symbolic link. + * Note: This is similar to `readlinkat` in POSIX. + * @return + * The number of bytes placed in the buffer. + */ +path_readlink :: proc "c" ( + fd: fd_t, + /** + * The path of the symbolic link from which to read. + */ + path: string, + /** + * The buffer to which to write the contents of the symbolic link. + */ + buf: []u8, +) -> (n: size_t, err: errno_t) { + err = wasi_path_readlink(fd, raw_data(path), len(path), raw_data(buf), len(buf), &n) + return +} +/** + * Concurrently poll for the occurrence of a set of events. + * @return + * The number of events stored. + */ +poll_oneoff :: proc "c" ( + /** + * The events to which to subscribe. + */ + subscription_in: ^subscription_t, + /** + * The events that have occurred. + */ + event_out: ^event_t, + /** + * Both the number of subscriptions and events. + */ + nsubscriptions: size_t, +) -> (n: size_t, err: errno_t) { + err = wasi_poll_oneoff(subscription_in, event_out, nsubscriptions, &n) + return +} +/** + * Receive a message from a socket. + * Note: This is similar to `recv` in POSIX, though it also supports reading + * the data into multiple buffers in the manner of `readv`. + * @return + * Number of bytes stored in ri_data and message flags. + */ +sock_recv :: proc "c" ( + fd: fd_t, + /** + * List of scatter/gather vectors to which to store data. + */ + ri_data: []iovec_t, + /** + * Message flags. + */ + ri_flags: riflags_t, +) -> (n: size_t, flags: roflags_t, err: errno_t) { + err = wasi_sock_recv(fd, raw_data(ri_data), len(ri_data), ri_flags, &n, &flags) + return +} +/** + * Send a message on a socket. + * Note: This is similar to `send` in POSIX, though it also supports writing + * the data from multiple buffers in the manner of `writev`. + * @return + * Number of bytes transmitted. + */ +sock_send :: proc "c" ( + fd: fd_t, + /** + * List of scatter/gather vectors to which to retrieve data + */ + si_data: []ciovec_t, + /** + * Message flags. + */ + si_flags: siflags_t, +) -> (n: size_t, err: errno_t) { + err = wasi_sock_send(fd, raw_data(si_data), len(si_data), si_flags, &n) + return +} + +/** + * Return a description of the given preopened file descriptor. + */ +fd_prestat_dir_name :: proc( + fd: fd_t, + /** + * A buffer into which to write the preopened directory name. + */ + path: string, +) -> errno_t { + return wasm_fd_prestat_dir_name(fd, raw_data(path), len(path)) +} +/** + * Create a directory. + * Note: This is similar to `mkdirat` in POSIX. + */ +path_create_directory :: proc( + fd: fd_t, + /** + * The path at which to create the directory. + */ + path: string, +) -> errno_t { + return wasm_path_create_directory(fd, raw_data(path), len(path)) +} +/** + * Adjust the timestamps of a file or directory. + * Note: This is similar to `utimensat` in POSIX. + */ +path_filestat_set_times :: proc( + fd: fd_t, + /** + * Flags determining the method of how the path is resolved. + */ + flags: lookupflags_t, + /** + * The path of the file or directory to operate on. + */ + path: string, + /** + * The desired values of the data access timestamp. + */ + atim: timestamp_t, + /** + * The desired values of the data modification timestamp. + */ + mtim: timestamp_t, + /** + * A bitmask indicating which timestamps to adjust. + */ + fst_flags: fstflags_t, +) -> errno_t { + return wasm_path_filestat_set_times(fd, flags, raw_data(path), len(path), atim, mtim, fst_flags) +} +/** + * Remove a directory. + * Return `errno::notempty` if the directory is not empty. + * Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + */ +path_remove_directory :: proc( + fd: fd_t, + /** + * The path to a directory to remove. + */ + path: string, +) -> errno_t { + return wasm_path_remove_directory(fd, raw_data(path), len(path)) +} +/** + * Create a hard link. + * Note: This is similar to `linkat` in POSIX. + */ +path_link :: proc( + old_fd: fd_t, + /** + * Flags determining the method of how the path is resolved. + */ + old_flags: lookupflags_t, + /** + * The source path from which to link. + */ + old_path: string, + /** + * The working directory at which the resolution of the new path starts. + */ + new_fd: fd_t, + /** + * The destination path at which to create the hard link. + */ + new_path: string, +) -> errno_t { + return wasm_path_link(old_fd, old_flags, raw_data(old_path), len(old_path), new_fd, raw_data(new_path), len(new_path)) +} + +/** + * Rename a file or directory. + * Note: This is similar to `renameat` in POSIX. + */ +path_rename :: proc( + fd: fd_t, + /** + * The source path of the file or directory to rename. + */ + old_path: string, + /** + * The working directory at which the resolution of the new path starts. + */ + new_fd: fd_t, + /** + * The destination path to which to rename the file or directory. + */ + new_path: string, +) -> errno_t { + return wasm_path_rename(fd, raw_data(old_path), len(old_path), new_fd, raw_data(new_path), len(new_path)) +} +/** + * Create a symbolic link. + * Note: This is similar to `symlinkat` in POSIX. + */ +path_symlink :: proc( + /** + * The contents of the symbolic link. + */ + old_path: string, + fd: fd_t, + /** + * The destination path at which to create the symbolic link. + */ + new_path: string, +) -> errno_t { + return wasm_path_symlink(raw_data(old_path), len(old_path), fd, raw_data(new_path), len(new_path)) +} +/** + * Unlink a file. + * Return `errno::isdir` if the path refers to a directory. + * Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + */ +path_unlink_file :: proc( + fd: fd_t, + /** + * The path to a file to unlink. + */ + path: string, +) -> errno_t { + return wasm_path_unlink_file(fd, raw_data(path), len(path)) +} +/** + * Write high-quality random data into a buffer. + * This function blocks when the implementation is unable to immediately + * provide sufficient high-quality random data. + * This function may execute slowly, so when large mounts of random data are + * required, it's advisable to use this function to seed a pseudo-random + * number generator, rather than to provide the random data directly. + */ +random_get :: proc( + /** + * The buffer to fill with random data. + */ + buf: []u8, +) -> errno_t { + return wasm_random_get(raw_data(buf), len(buf)) +} + + + + +@(default_calling_convention="c") +foreign wasi { + @(link_name="args_sizes_get") + wasi_args_sizes_get :: proc( + retptr0: ^size_t, + retptr1: ^size_t, + ) -> errno_t --- + @(link_name="environ_sizes_get") + wasi_environ_sizes_get :: proc( + retptr0: ^size_t, + retptr1: ^size_t, + ) -> errno_t --- + @(link_name="clock_res_get") + wasi_clock_res_get :: proc( + id: clockid_t, + retptr0: ^timestamp_t, + ) -> errno_t --- + @(link_name="clock_time_get") + wasi_clock_time_get :: proc( + id: clockid_t, + precision: timestamp_t, + retptr0: ^timestamp_t, + ) -> errno_t --- + @(link_name="fd_fdstat_get") + wasi_fd_fdstat_get :: proc( + fd: fd_t, + retptr0: ^fdstat_t, + ) -> errno_t --- + @(link_name="fd_filestat_get") + wasi_fd_filestat_get :: proc( + fd: fd_t, + retptr0: ^filestat_t, + ) -> errno_t --- + @(link_name="fd_pread") + wasi_fd_pread :: proc( + fd: fd_t, + iovs: [^]iovec_t, + iovs_len: size_t, + offset: filesize_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="fd_prestat_get") + wasi_fd_prestat_get :: proc( + fd: fd_t, + retptr0: ^prestat_t, + ) -> errno_t --- + @(link_name="fd_pwrite") + wasi_fd_pwrite :: proc( + fd: fd_t, + iovs: [^]ciovec_t, + iovs_len: size_t, + offset: filesize_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="fd_read") + wasi_fd_read :: proc( + fd: fd_t, + iovs: [^]iovec_t, + iovs_len: size_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="fd_readdir") + wasi_fd_readdir :: proc( + fd: fd_t, + buf: [^]u8, + buf_len: size_t, + cookie: dircookie_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="fd_seek") + wasi_fd_seek :: proc( + fd: fd_t, + offset: filedelta_t, + whence: whence_t, + retptr0: ^filesize_t, + ) -> errno_t --- + @(link_name="fd_tell") + wasi_fd_tell :: proc( + fd: fd_t, + retptr0: ^filesize_t, + ) -> errno_t --- + @(link_name="fd_write") + wasi_fd_write :: proc( + fd: fd_t, + iovs: [^]ciovec_t, + iovs_len: size_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="path_filestat_get") + wasi_path_filestat_get :: proc( + fd: fd_t, + flags: lookupflags_t, + /** + * The path of the file or directory to inspect. + */ + path: [^]u8, + path_len: size_t, + retptr0: ^filestat_t, + ) -> errno_t --- + @(link_name="path_open") + wasi_path_open :: proc( + fd: fd_t, + dirflags: lookupflags_t, + path: [^]u8, + path_len: size_t, + oflags: oflags_t, + fs_rights_base: rights_t, + fs_rights_inheriting: rights_t, + fdflags: fdflags_t, + retptr: ^fd_t, + ) -> errno_t --- + @(link_name="path_readlink") + wasi_path_readlink :: proc( + fd: fd_t, + path: [^]u8, + path_len: size_t, + buf: [^]u8, + buf_len: size_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="poll_oneoff") + wasi_poll_oneoff :: proc( + subscription_in: ^subscription_t, + event_out: ^event_t, + nsubscriptions: size_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="sock_recv") + wasi_sock_recv :: proc( + fd: fd_t, + ri_data: [^]iovec_t, + ri_data_len: size_t, + ri_flags: riflags_t, + retptr0: ^size_t, + retptr1: ^roflags_t, + ) -> errno_t --- + @(link_name="sock_send") + wasi_sock_send :: proc( + fd: fd_t, + si_data: [^]ciovec_t, + si_data_len: size_t, + si_flags: siflags_t, + retptr0: ^size_t, + ) -> errno_t --- + @(link_name="fd_prestat_dir_name") + wasm_fd_prestat_dir_name :: proc( + fd: fd_t, + path: [^]u8, + path_len: size_t, + ) -> errno_t --- + @(link_name="path_create_directory") + wasm_path_create_directory :: proc( + fd: fd_t, + path: [^]u8, + path_len: size_t, + ) -> errno_t --- + @(link_name="path_filestat_set_times") + wasm_path_filestat_set_times :: proc( + fd: fd_t, + flags: lookupflags_t, + path: [^]u8, + path_len: size_t, + atim: timestamp_t, + mtim: timestamp_t, + fst_flags: fstflags_t, + ) -> errno_t --- + @(link_name="path_remove_directory") + wasm_path_remove_directory :: proc( + fd: fd_t, + path: [^]u8, + path_len: size_t, + ) -> errno_t --- + @(link_name="path_link") + wasm_path_link :: proc( + old_fd: fd_t, + old_flags: lookupflags_t, + old_path: [^]u8, + old_path_len: size_t, + new_fd: fd_t, + new_path: [^]u8, + new_path_len: size_t, + ) -> errno_t --- + @(link_name="path_rename") + wasm_path_rename :: proc( + fd: fd_t, + old_path: [^]u8, + old_path_len: size_t, + new_fd: fd_t, + new_path: [^]u8, + new_path_len: size_t, + ) -> errno_t --- + @(link_name="path_symlink") + wasm_path_symlink :: proc( + old_path: [^]u8, + old_path_len: size_t, + fd: fd_t, + new_path: [^]u8, + new_path_len: size_t, + ) -> errno_t --- + @(link_name="path_unlink_file") + wasm_path_unlink_file :: proc( + fd: fd_t, + path: [^]u8, + path_len: size_t, + ) -> errno_t --- + @(link_name="random_get") + wasm_random_get :: proc( + buf: [^]u8, + buf_len: size_t, + ) -> errno_t --- +} diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 69e1ec5f0..3bd6622c7 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -16,6 +16,8 @@ enum TargetOsKind { TargetOs_linux, TargetOs_essence, TargetOs_freebsd, + + TargetOs_wasi, TargetOs_freestanding, @@ -29,6 +31,7 @@ enum TargetArchKind { TargetArch_386, TargetArch_arm64, TargetArch_wasm32, + TargetArch_wasm64, TargetArch_COUNT, }; @@ -49,6 +52,8 @@ String target_os_names[TargetOs_COUNT] = { str_lit("linux"), str_lit("essence"), str_lit("freebsd"), + + str_lit("wasi"), str_lit("freestanding"), }; @@ -59,6 +64,7 @@ String target_arch_names[TargetArch_COUNT] = { str_lit("386"), str_lit("arm64"), str_lit("wasm32"), + str_lit("wasm64"), }; String target_endian_names[TargetEndian_COUNT] = { @@ -72,6 +78,7 @@ TargetEndianKind target_endians[TargetArch_COUNT] = { TargetEndian_Little, TargetEndian_Little, TargetEndian_Little, + TargetEndian_Little, }; #ifndef ODIN_VERSION_RAW @@ -335,6 +342,26 @@ gb_global TargetMetrics target_freestanding_wasm32 = { str_lit(""), }; +gb_global TargetMetrics target_freestanding_wasm64 = { + TargetOs_freestanding, + TargetArch_wasm64, + 8, + 16, + str_lit("wasm64-freestanding-js"), + str_lit(""), +}; + +gb_global TargetMetrics target_wasi_wasm32 = { + TargetOs_wasi, + TargetArch_wasm32, + 4, + 8, + str_lit("wasm32-wasi-js"), + str_lit(""), +}; + + + struct NamedTargetMetrics { @@ -353,6 +380,8 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_386"), &target_freebsd_386 }, { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, + // { str_lit("freestanding_wasm64"), &target_freestanding_wasm64 }, + { str_lit("wasi_wasm32"), &target_wasi_wasm32 }, }; NamedTargetMetrics *selected_target_metrics; @@ -458,11 +487,21 @@ bool find_library_collection_path(String name, String *path) { } bool is_arch_wasm(void) { - return build_context.metrics.arch == TargetArch_wasm32; + switch (build_context.metrics.arch) { + case TargetArch_wasm32: + case TargetArch_wasm64: + return true; + } + return false; } bool allow_check_foreign_filepath(void) { - return build_context.metrics.arch != TargetArch_wasm32; + switch (build_context.metrics.arch) { + case TargetArch_wasm32: + case TargetArch_wasm64: + return false; + } + return true; } @@ -869,11 +908,24 @@ void init_build_context(TargetMetrics *cross_target) { bc->link_flags = str_lit("-arch arm64 "); break; } - - } else if (bc->metrics.arch == TargetArch_wasm32) { - bc->link_flags = str_lit("--no-entry --export-table --export-all --allow-undefined "); + } else if (is_arch_wasm()) { + gbString link_flags = gb_string_make(heap_allocator(), " "); + // link_flags = gb_string_appendc(link_flags, "--export-all "); + // link_flags = gb_string_appendc(link_flags, "--export-table "); + link_flags = gb_string_appendc(link_flags, "--allow-undefined "); + if (bc->metrics.arch == TargetArch_wasm64) { + link_flags = gb_string_appendc(link_flags, "-mwas64 "); + } + if (bc->metrics.os == TargetOs_freestanding) { + link_flags = gb_string_appendc(link_flags, "--no-entry "); + } + + bc->link_flags = make_string_c(link_flags); + + // Disallow on wasm + build_context.use_separate_modules = false; } else { - gb_printf_err("Compiler Error: Unsupported architecture\n");; + gb_printf_err("Compiler Error: Unsupported architecture\n"); gb_exit(1); } diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 0591eca4d..c2d23e70c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -899,6 +899,10 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { mutex_unlock(&ctx->info->foreign_mutex); } } + + if (e->Procedure.link_name.len > 0 ) { + e->flags |= EntityFlag_CustomLinkName; + } } void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, Ast *init_expr) { @@ -990,6 +994,10 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, string_map_set(fp, key, e); } } + + if (e->Variable.link_name.len > 0) { + e->flags |= EntityFlag_CustomLinkName; + } if (init_expr == nullptr) { if (type_expr == nullptr) { diff --git a/src/checker.cpp b/src/checker.cpp index 8db9e1bd6..f0f463816 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2011,6 +2011,9 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { str_lit("gnu_h2f_ieee"), str_lit("gnu_f2h_ieee"), str_lit("extendhfsf2"), + + // WASM Specific + str_lit("__ashlti3"), }; for (isize i = 0; i < gb_count_of(required_runtime_entities); i++) { force_add_dependency_entity(c, c->info.runtime_package->scope, required_runtime_entities[i]); diff --git a/src/entity.cpp b/src/entity.cpp index 86fefcf89..d95c74f22 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -74,9 +74,10 @@ enum EntityFlag : u64 { EntityFlag_Test = 1ull<<30, EntityFlag_Init = 1ull<<31, + + EntityFlag_CustomLinkName = 1ull<<40, EntityFlag_Overridden = 1ull<<63, - }; enum EntityState : u32 { diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 9e7f4b290..9c7ced91e 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1039,6 +1039,75 @@ namespace lbAbiArm64 { } } +namespace lbAbiWasm32 { + Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); + lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); + + LB_ABI_INFO(abi_info) { + lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); + ft->ctx = c; + ft->args = compute_arg_types(c, arg_types, arg_count); + ft->ret = compute_return_type(c, return_type, return_is_defined); + ft->calling_convention = calling_convention; + return ft; + } + + lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) { + if (!is_return && type == LLVMIntTypeInContext(c, 128)) { + LLVMTypeRef cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); + return lb_arg_type_direct(type, cast_type, nullptr, nullptr); + } + + if (!is_return && lb_sizeof(type) > 8) { + return lb_arg_type_indirect(type, nullptr); + } + + LLVMAttributeRef attr = nullptr; + LLVMTypeRef i1 = LLVMInt1TypeInContext(c); + if (type == i1) { + attr = lb_create_enum_attribute(c, "zeroext"); + } + return lb_arg_type_direct(type, nullptr, nullptr, attr); + } + + Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { + auto args = array_make(heap_allocator(), arg_count); + + for (unsigned i = 0; i < arg_count; i++) { + LLVMTypeRef t = arg_types[i]; + LLVMTypeKind kind = LLVMGetTypeKind(t); + i64 sz = lb_sizeof(t); + if (kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind) { + if (sz == 0) { + args[i] = lb_arg_type_ignore(t); + } else { + args[i] = lb_arg_type_indirect(t, nullptr); + } + } else { + args[i] = non_struct(c, t, false); + } + } + return args; + } + + lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined) { + if (!return_is_defined) { + return lb_arg_type_direct(LLVMVoidTypeInContext(c)); + } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { + i64 sz = lb_sizeof(return_type); + switch (sz) { + case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); + case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); + case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); + case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); + } + LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); + return lb_arg_type_indirect(return_type, attr); + } + return non_struct(c, return_type, true); + } +} + LB_ABI_INFO(lb_get_abi_info) { switch (calling_convention) { @@ -1061,19 +1130,27 @@ LB_ABI_INFO(lb_get_abi_info) { } } - if (build_context.metrics.arch == TargetArch_amd64) { + switch (build_context.metrics.arch) { + case TargetArch_amd64: if (build_context.metrics.os == TargetOs_windows) { return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } else { return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } - } else if (build_context.metrics.arch == TargetArch_386) { + case TargetArch_386: return lbAbi386::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); - } else if (build_context.metrics.arch == TargetArch_arm64) { + case TargetArch_arm64: return lbAbiArm64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); - } else if (build_context.metrics.arch == TargetArch_wasm32) { - return lbAbi386::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); + case TargetArch_wasm32: + // TODO(bill): implement wasm32's ABI correct + // NOTE(bill): this ABI is only an issue for WASI compatibility + return lbAbiWasm32::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); + case TargetArch_wasm64: + // TODO(bill): implement wasm64's ABI correct + // NOTE(bill): this ABI is only an issue for WASI compatibility + return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } + GB_PANIC("Unsupported ABI"); return {}; } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 4d1245c98..1d382aa6d 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -771,6 +771,8 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) Type *results = alloc_type_tuple(); Type *t_ptr_cstring = alloc_type_pointer(t_cstring); + + bool call_cleanup = true; bool has_args = false; bool is_dll_main = false; @@ -782,8 +784,12 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) params->Tuple.variables[0] = alloc_entity_param(nullptr, make_token_ident("hinstDLL"), t_rawptr, false, true); params->Tuple.variables[1] = alloc_entity_param(nullptr, make_token_ident("fdwReason"), t_u32, false, true); params->Tuple.variables[2] = alloc_entity_param(nullptr, make_token_ident("lpReserved"), t_rawptr, false, true); + call_cleanup = false; } else if (build_context.metrics.os == TargetOs_windows && build_context.metrics.arch == TargetArch_386) { name = str_lit("mainCRTStartup"); + } else if (is_arch_wasm()) { + name = str_lit("_start"); + call_cleanup = false; } else { has_args = true; slice_init(¶ms->Tuple.variables, permanent_allocator(), 2); @@ -874,8 +880,10 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) } - lbValue cleanup_runtime_value = lb_find_runtime_value(m, str_lit("_cleanup_runtime")); - lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none, false); + if (call_cleanup) { + lbValue cleanup_runtime_value = lb_find_runtime_value(m, str_lit("_cleanup_runtime")); + lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none, false); + } if (is_dll_main) { @@ -885,6 +893,19 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) } lb_end_procedure_body(p); + + + if (is_arch_wasm()) { + LLVMSetLinkage(p->value, LLVMDLLExportLinkage); + LLVMSetDLLStorageClass(p->value, LLVMDLLExportStorageClass); + LLVMSetVisibility(p->value, LLVMDefaultVisibility); + + char const *export_name = alloc_cstring(permanent_allocator(), p->name); + LLVMAddTargetDependentFunctionAttr(p->value, "wasm-export-name", export_name); + } else { + LLVMSetLinkage(p->value, LLVMExternalLinkage); + } + if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %s\n", "main"); @@ -1064,14 +1085,10 @@ struct lbLLVMModulePassWorkerData { }; WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { - GB_ASSERT(MULTITHREAD_OBJECT_GENERATION); - auto wd = cast(lbLLVMModulePassWorkerData *)data; - LLVMPassManagerRef module_pass_manager = LLVMCreatePassManager(); lb_populate_module_pass_manager(wd->target_machine, module_pass_manager, build_context.optimization_level); LLVMRunPassManager(module_pass_manager, wd->m->mod); - return 0; } @@ -1148,6 +1165,7 @@ void lb_generate_code(lbGenerator *gen) { LLVMInitializeAArch64Disassembler(); break; case TargetArch_wasm32: + case TargetArch_wasm64: LLVMInitializeWebAssemblyTargetInfo(); LLVMInitializeWebAssemblyTarget(); LLVMInitializeWebAssemblyTargetMC(); @@ -1660,6 +1678,8 @@ void lb_generate_code(lbGenerator *gen) { for_array(i, gen->modules.entries) { lbModule *m = gen->modules.entries[i].value; + + lb_run_remove_unused_function_pass(m->mod); auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); wd->m = m; @@ -1737,8 +1757,16 @@ void lb_generate_code(lbGenerator *gen) { } TIME_SECTION("LLVM Object Generation"); + + isize non_empty_module_count = 0; + for_array(j, gen->modules.entries) { + lbModule *m = gen->modules.entries[j].value; + if (!lb_is_module_empty(m)) { + non_empty_module_count += 1; + } + } - if (do_threading) { + if (do_threading && non_empty_module_count > 1) { for_array(j, gen->modules.entries) { lbModule *m = gen->modules.entries[j].value; if (lb_is_module_empty(m)) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 9aa9920f2..5fc5dfebf 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -585,3 +585,24 @@ enum : LLVMAttributeIndex { LLVMAttributeIndex_FunctionIndex = ~0u, LLVMAttributeIndex_FirstArgIndex = 1, }; + + +char const *llvm_linkage_strings[] = { + "external linkage", + "available externally linkage", + "link once any linkage", + "link once odr linkage", + "link once odr auto hide linkage", + "weak any linkage", + "weak odr linkage", + "appending linkage", + "internal linkage", + "private linkage", + "dllimport linkage", + "dllexport linkage", + "external weak linkage", + "ghost linkage", + "common linkage", + "linker private linkage", + "linker private weak linkage" +}; \ No newline at end of file diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index c9827ae3a..fd9b10a4f 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -496,6 +496,7 @@ bool lb_is_matrix_simdable(Type *t) { break; case TargetArch_386: case TargetArch_wasm32: + case TargetArch_wasm64: // nope return false; } @@ -513,6 +514,7 @@ bool lb_is_matrix_simdable(Type *t) { return true; case TargetArch_386: case TargetArch_wasm32: + case TargetArch_wasm64: return false; } } diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index d5ea90aea..75a377e5b 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -355,3 +355,63 @@ void lb_run_function_pass_manager(LLVMPassManagerRef fpm, lbProcedure *p) { // are not removed lb_run_remove_dead_instruction_pass(p); } + + +void lb_run_remove_unused_function_pass(LLVMModuleRef mod) { + isize removal_count = 0; + isize pass_count = 0; + isize const max_pass_count = 10; + // Custom remove dead function pass + for (; pass_count < max_pass_count; pass_count++) { + bool was_dead_function = false; + for (LLVMValueRef func = LLVMGetFirstFunction(mod); + func != nullptr; + /**/ + ) { + LLVMValueRef curr_func = func; + func = LLVMGetNextFunction(func); + + LLVMUseRef first_use = LLVMGetFirstUse(curr_func); + if (first_use != nullptr) { + continue; + } + String name = {}; + name.text = cast(u8 *)LLVMGetValueName2(curr_func, cast(size_t *)&name.len); + + if (LLVMIsDeclaration(curr_func)) { + // Ignore for the time being + continue; + } + + if (name == "memset" || + name == "memmove" || + name == "memcpy") { + continue; + } + if (is_arch_wasm()) { + if (name == "__ashlti3") { + LLVMSetLinkage(curr_func, LLVMExternalLinkage); + continue; + } + } + + LLVMLinkage linkage = LLVMGetLinkage(curr_func); + + switch (linkage) { + case LLVMExternalLinkage: + case LLVMDLLImportLinkage: + case LLVMDLLExportLinkage: + default: + continue; + case LLVMInternalLinkage: + break; + } + LLVMDeleteFunction(curr_func); + was_dead_function = true; + removal_count += 1; + } + if (!was_dead_function) { + break; + } + } +} diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 15689da36..29f7b6655 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -195,13 +195,19 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) // then it is very likely it is required by LLVM and thus cannot have internal linkage if (entity->pkg != nullptr && entity->pkg->kind == Package_Runtime && p->body != nullptr) { GB_ASSERT(entity->kind == Entity_Procedure); - if (entity->Procedure.link_name != "") { - LLVMSetLinkage(p->value, LLVMExternalLinkage); + String link_name = entity->Procedure.link_name; + if (entity->flags & EntityFlag_CustomLinkName && + link_name != "") { + if (string_starts_with(link_name, str_lit("__"))) { + LLVMSetLinkage(p->value, LLVMExternalLinkage); + } else { + LLVMSetLinkage(p->value, LLVMInternalLinkage); + } } } } } - + if (p->is_foreign) { if (is_arch_wasm()) { char const *import_name = alloc_cstring(permanent_allocator(), p->name); @@ -217,6 +223,7 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) LLVMAddTargetDependentFunctionAttr(p->value, "wasm-import-module", module_name); } } + // NOTE(bill): offset==0 is the return value isize offset = 1; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 14fb8280e..b58f07d49 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -1504,6 +1504,7 @@ lbValue lb_emit_mul_add(lbProcedure *p, lbValue a, lbValue b, lbValue c, Type *t break; case TargetArch_386: case TargetArch_wasm32: + case TargetArch_wasm64: is_possible = false; break; } diff --git a/src/main.cpp b/src/main.cpp index 92e541384..173c70a4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,13 +135,12 @@ i32 linker_stage(lbGenerator *gen) { if (is_arch_wasm()) { timings_start_section(timings, str_lit("wasm-ld")); + result = system_exec_command_line_app("wasm-ld", - "\"%.*s\\bin\\wasm-ld\" \"%.*s.wasm-obj\" -o \"%.*s.wasm\" %.*s %.*s", + "\"%.*s\\bin\\wasm-ld\" \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s", LIT(build_context.ODIN_ROOT), LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); - if (result) { - return result; - } + return result; } if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) { diff --git a/src/string.cpp b/src/string.cpp index ca53fb2fc..800378689 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -21,13 +21,14 @@ struct String { }; // NOTE(bill): used for printf style arguments #define LIT(x) ((int)(x).len), (x).text -#define STR_LIT(c_str) {cast(u8 *)c_str, gb_size_of(c_str)-1} #if defined(GB_COMPILER_MSVC) && _MSC_VER < 1700 - #define str_lit(c_str) make_string(cast(u8 *)c_str, gb_size_of(c_str)-1) + #define STR_LIT(c_str) make_string(cast(u8 *)c_str, gb_size_of(c_str)-1) #else - #define str_lit(c_str) String{cast(u8 *)c_str, gb_size_of(c_str)-1} + #define STR_LIT(c_str) String{cast(u8 *)c_str, gb_size_of(c_str)-1} #endif +#define str_lit(c_str) STR_LIT(c_str) + // NOTE(bill): String16 is only used for Windows due to its file directories struct String16 { wchar_t *text;