From: Brendan Hansen Date: Sun, 5 Mar 2023 04:23:50 +0000 (-0600) Subject: cleanup: restructured platform layer code X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=52ef74ffdc93c9f0f6ead8852b8164d9e650a66c;p=onyx.git cleanup: restructured platform layer code Platform layer code was scattered and undocumented what was expected. This has been remedied, and a good baseline has been defined. --- diff --git a/core/io/stdio.onyx b/core/io/stdio.onyx index 42355ee3..4aeefbc9 100644 --- a/core/io/stdio.onyx +++ b/core/io/stdio.onyx @@ -53,7 +53,7 @@ __flush_stdio :: () { ^stdio.print_stream |> io.buffer_stream_to_str() - |> runtime.__output_string(); + |> runtime.platform.__output_string(); ^stdio.print_stream |> io.stream_flush(); } @@ -92,17 +92,17 @@ printf :: (format: str, va: ..any) { print(conv.format_va(buffer, format, va, .{null, flush})); } -#if #defined(runtime.__output_error) { +#if #defined(runtime.platform.__output_error) { // // Prints to standard error, if available. eprintf :: (format: str, va: ..any) -> str { flush :: (_, to_output) => { - runtime.__output_error(to_output); + runtime.platform.__output_error(to_output); return true; } buffer: [1024] u8; - runtime.__output_error(conv.format_va(buffer, format, va, .{null, flush})); + runtime.platform.__output_error(conv.format_va(buffer, format, va, .{null, flush})); } } @@ -141,9 +141,9 @@ __byte_dump :: (ptr: rawptr, byte_count: u32, bytes_per_line := 8) { temp[1] = map_to_ascii(val & 15); temp[2] = #char " "; - runtime.__output_string(~~temp); + runtime.platform.__output_string(~~temp); - if i % bytes_per_line == (bytes_per_line - 1) do runtime.__output_string("\n"); + if i % bytes_per_line == (bytes_per_line - 1) do runtime.platform.__output_string("\n"); } @@ -180,7 +180,7 @@ __byte_dump :: (ptr: rawptr, byte_count: u32, bytes_per_line := 8) { #local stdio_vtable := io.Stream_Vtable.{ read = (_: ^io.Stream, buffer: [] u8) -> (io.Error, u32) { __flush_stdio(); - bytes_read := runtime.__read_from_input(buffer); + bytes_read := runtime.platform.__read_from_input(buffer); if bytes_read == 0 do return .ReadPending, 0; if bytes_read < 0 do return .EOF, 0; @@ -190,7 +190,7 @@ __byte_dump :: (ptr: rawptr, byte_count: u32, bytes_per_line := 8) { read_byte = (_: ^io.Stream) -> (io.Error, u8) { __flush_stdio(); buf: [1] u8; - bytes_read := runtime.__read_from_input(buf); + bytes_read := runtime.platform.__read_from_input(buf); if bytes_read <= 0 do return .EOF, 0; return .None, buf[0]; diff --git a/core/net/net.onyx b/core/net/net.onyx index 51d4d719..1b2569b4 100644 --- a/core/net/net.onyx +++ b/core/net/net.onyx @@ -1,5 +1,9 @@ package core.net +#if !runtime.platform.Supports_Networking { + #error "Cannot include this file. Platform not supported."; +} + use core Socket :: struct { @@ -281,6 +285,11 @@ network_to_host :: #match #local {} } }; +// +// This will be moved into the platform layer later. +// I think this is most everything you would need, but +// I need to see how WASI does sockets to see if this +// makes sense as an abstraction. #foreign "onyx_runtime" { #package __net_create_socket :: (out_handle: ^Socket.Handle, domain: SocketDomain, type: SocketType) -> SocketError --- #package __net_close_socket :: (handle: Socket.Handle) -> void --- diff --git a/core/net/tcp.onyx b/core/net/tcp.onyx index 696501b9..98479904 100644 --- a/core/net/tcp.onyx +++ b/core/net/tcp.onyx @@ -1,5 +1,9 @@ package core.net +#if !runtime.platform.Supports_Networking { + #error "Cannot include this file. Platform not supported."; +} + use core {sync, thread, array, memory, alloc, os, iter} #if !runtime.Multi_Threading_Enabled { diff --git a/core/onyx/fs.onyx b/core/onyx/fs.onyx deleted file mode 100644 index 7e431798..00000000 --- a/core/onyx/fs.onyx +++ /dev/null @@ -1,103 +0,0 @@ -package runtime.fs - -use package core - -FileData :: #distinct i64 -DirectoryData :: #distinct u64 - -__file_open :: (path: str, mode := os.OpenMode.Read) -> (FileData, os.FileError) { - handle: FileData; - error := __file_open_impl(path, mode, ^handle); - - return handle, error; -} - -#foreign "onyx_runtime" { - __file_open_impl :: (path: str, mode: os.OpenMode, out_handle: ^FileData) -> os.FileError --- - - __file_close :: (fd: FileData) -> os.FileError --- - __file_stat :: (path: str, stat: ^os.FileStat) -> bool --- - __file_exists :: (path: str) -> bool --- - __file_remove :: (path: str) -> bool --- - __file_rename :: (old_path: str, new_path: str) -> bool --- - - __file_seek :: (handle: FileData, to: i32, whence: io.SeekFrom) -> i32 --- - __file_tell :: (handle: FileData) -> u32 --- - __file_read :: (handle: FileData, output_buffer: [] u8, bytes_read: ^u64) -> io.Error --- - __file_write :: (handle: FileData, input_buffer: [] u8, bytes_wrote: ^u64) -> io.Error --- - __file_flush :: (handle: FileData) -> io.Error --- - __file_size :: (handle: FileData) -> u32 --- - - __dir_open :: (path: str, dir: ^DirectoryData) -> bool --- - __dir_close :: (dir: DirectoryData) -> void --- - __dir_read :: (dir: DirectoryData, out_entry: ^os.DirectoryEntry) -> bool --- - __dir_create :: (path: str) -> bool --- - __dir_remove :: (path: str) -> bool --- - - - __enable_non_blocking_stdin :: () -> void --- -} - -__file_stream_vtable := io.Stream_Vtable.{ - seek = (use fs: ^os.File, to: i32, whence: io.SeekFrom) -> io.Error { - now := __file_seek(data, to, whence); - return (.None) if now >= 0 else .BadFile; - }, - - tell = (use fs: ^os.File) -> (io.Error, u32) { - return .None, __file_tell(data); - }, - - read = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { - bytes_read: u64; - error := __file_read(data, buffer, ^bytes_read); - return error, ~~bytes_read; - }, - - read_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { - __file_seek(data, at, .Start); - bytes_read: u64; - error := __file_read(data, buffer, ^bytes_read); - return error, ~~bytes_read; - }, - - read_byte = (use fs: ^os.File) -> (io.Error, u8) { - byte: u8; - error := __file_read(data, ~~ cast([1] u8) ^byte, null); - return error, byte; - }, - - write = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { - bytes_wrote: u64; - error := __file_write(data, buffer, ^bytes_wrote); - return error, ~~bytes_wrote; - }, - - write_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { - __file_seek(data, at, .Start); - bytes_wrote: u64; - error := __file_write(data, buffer, ^bytes_wrote); - return error, ~~bytes_wrote; - }, - - write_byte = (use fs: ^os.File, byte: u8) -> io.Error { - b := byte; - bytes_wrote: u64; - error := __file_write(data, .{ ^b, 1 }, ^bytes_wrote); - return error; - }, - - close = (use fs: ^os.File) -> io.Error { - __file_close(data); - return .None; - }, - - flush = (use fs: ^os.File) -> io.Error { - return __file_flush(data); - }, - - size = (use fs: ^os.File) -> i32 { - return __file_size(data); - }, -}; - diff --git a/core/os/dir.onyx b/core/os/dir.onyx index 4ec08942..a8aaeeac 100644 --- a/core/os/dir.onyx +++ b/core/os/dir.onyx @@ -1,6 +1,11 @@ package core.os -use runtime {fs} +#if !runtime.platform.Supports_Directories { + #error "Cannot include this file. Platform not supported."; +} + +use core {string} +#local fs :: runtime.platform Directory :: fs.DirectoryData; @@ -31,3 +36,52 @@ dir_create :: fs.__dir_create dir_exists :: fs.__file_exists dir_remove :: fs.__dir_remove dir_rename :: fs.__file_rename + +list_directory :: (path: str) -> Iterator(DirectoryEntry) { + Context :: struct { + dir: Directory; + opened := false; + } + + next :: (use c: ^Context) -> (DirectoryEntry, bool) { + if !opened do return .{}, false; + + entry: DirectoryEntry; + if !dir_read(dir, ^entry) { + return .{}, false; + } + + return entry, true; + } + + close :: (use c: ^Context) { + dir_close(dir); + cfree(c); + } + + c := new(Context); + if dir, success := dir_open(path); success { + c.dir = dir; + c.opened = true; + } + + return .{ c, next, close }; +} + +remove_directory :: (path: str) -> bool { + // This is quite a bit of space, and could result in a stack overrun + // if the directory being deleted has a lot of descendents. + full_path_buffer: [512] u8; + + for list_directory(path) { + full_path := string.concat(full_path_buffer, path, "/", it->name()); + + if it.type == .Directory { + if !remove_directory(full_path) do return false; + } else { + remove_file(full_path); + } + } + + return dir_remove(path); +} diff --git a/core/os/file.onyx b/core/os/file.onyx index 277f99df..8d663c74 100644 --- a/core/os/file.onyx +++ b/core/os/file.onyx @@ -1,7 +1,12 @@ package core.os +#if !runtime.platform.Supports_Files { + #error "Cannot include this file. Platform not supported."; +} + use core -use runtime {fs} +#local fs :: package runtime.platform + FileError :: enum { None :: 0x00; diff --git a/core/os/os.onyx b/core/os/os.onyx index 171c193e..03c183e1 100644 --- a/core/os/os.onyx +++ b/core/os/os.onyx @@ -1,69 +1,21 @@ package core.os -#if runtime.runtime != .Wasi - && runtime.runtime != .Onyx { - #error "The os library is currently only available on the WASI runtime, and should only be included if that is the chosen runtime." -} - use core {string} -list_directory :: (path: str) -> Iterator(DirectoryEntry) { - Context :: struct { - dir: Directory; - opened := false; - } - - next :: (use c: ^Context) -> (DirectoryEntry, bool) { - if !opened do return .{}, false; - - entry: DirectoryEntry; - if !dir_read(dir, ^entry) { - return .{}, false; - } - - return entry, true; - } - - close :: (use c: ^Context) { - dir_close(dir); - cfree(c); - } - - c := new(Context); - if dir, success := dir_open(path); success { - c.dir = dir; - c.opened = true; - } - - return .{ c, next, close }; -} - -remove_directory :: (path: str) -> bool { - // This is quite a bit of space, and could result in a stack overrun - // if the directory being deleted has a lot of descendents. - full_path_buffer: [512] u8; - - for list_directory(path) { - full_path := string.concat(full_path_buffer, path, "/", it->name()); - - if it.type == .Directory { - if !remove_directory(full_path) do return false; - } else { - remove_file(full_path); - } - } - - return dir_remove(path); +#if !runtime.platform.Supports_Os { + #error "Cannot include this file. Platform not supported."; } exit :: (exitcode: i32) { - runtime.__exit(exitcode); + runtime.platform.__exit(exitcode); } -#if #defined(runtime.__sleep) { - sleep :: runtime.__sleep +#if #defined(runtime.platform.__sleep) { + sleep :: runtime.platform.__sleep } -#if #defined(runtime.__time) { - time :: runtime.__time +#if #defined(runtime.platform.__time) { + time :: runtime.platform.__time +} else { + time :: () => u64.{0}; } diff --git a/core/os/process.onyx b/core/os/process.onyx index 08ed4785..bc511ee8 100644 --- a/core/os/process.onyx +++ b/core/os/process.onyx @@ -1,16 +1,23 @@ package core.os -#if runtime.runtime != .Onyx { - #error "This file can only be included in the 'onyx' runtime, because Wasi has not defined how to spawn and manage processes."; +#if !runtime.platform.Supports_Processes { + #error "Cannot include this file. Platform not supported."; } use core {io} +use runtime.platform { + __process_spawn, + __process_destroy, + __process_kill, + __process_read, + __process_write, + __process_wait, + ProcessData +} Process :: struct { - Handle :: #distinct i64; - use stream: io.Stream; - process_handle: Handle; + process_handle: ProcessData; } process_spawn :: (path: str, args: [] str, non_blocking_io := false, starting_directory := "") -> Process { @@ -70,19 +77,10 @@ process_destroy :: (use p: ^Process) => { } } -#local ProcessResult :: enum { +ProcessResult :: enum { Success :: 0x00; FailedToRun :: 0x01; Error :: 0x02; InternalErr :: 0x03; } -#foreign "onyx_runtime" { - __process_spawn :: (path: str, args: [] str, non_blocking_io: bool, starting_directory: str) -> Process.Handle --- - __process_read :: (handle: Process.Handle, buffer: [] u8) -> i32 --- - __process_write :: (handle: Process.Handle, buffer: [] u8) -> i32 --- - __process_kill :: (handle: Process.Handle) -> bool --- - __process_wait :: (handle: Process.Handle) -> ProcessResult --- - __process_destroy :: (handle: Process.Handle) -> void --- -} - diff --git a/core/runtime/common.onyx b/core/runtime/common.onyx index bee7072e..759973b1 100644 --- a/core/runtime/common.onyx +++ b/core/runtime/common.onyx @@ -2,6 +2,13 @@ package runtime use core use core.intrinsics.onyx { __initialize } +use platform { __output_string } + +// +// Export the __start function from the platform layer. +// Every platform should define this, even if is it just '() {}'. +#export "_start" platform.__start + // The default assert handler. This assumes that __output_string // and __exit are defined in the 'runtime' package. @@ -83,4 +90,4 @@ __thread_initialize :: () { raw_free(alloc.heap_allocator, __tls_base); core.thread.__exited(id); } -} +} \ No newline at end of file diff --git a/core/runtime/js.onyx b/core/runtime/js.onyx deleted file mode 100644 index f13a5e7f..00000000 --- a/core/runtime/js.onyx +++ /dev/null @@ -1,34 +0,0 @@ -package runtime - -#load "core/runtime/common" - -use core - -__output_string :: (s: str) -> u32 #foreign "host" "print_str" --- -__exit :: (status: i32) -> void #foreign "host" "exit" --- -__read_from_input :: (buf: [] u8) -> u32 do return 0; - -// The builtin _start proc. -// Sets up everything needed for execution. -#export "_start" () { - __runtime_initialize(); - context.thread_id = 0; - - #if (typeof (package main).main) == #type () -> void { - (package main).main(); - - } else { - args: [] cstr = .{ null, 0 }; - (package main).main(args); - } - - __flush_stdio(); -} - -#if Multi_Threading_Enabled { - __spawn_thread :: (id: i32, tls_base: rawptr, func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "host" "spawn_thread" --- - __kill_thread :: (id: i32) -> i32 #foreign "host" "kill_thread" --- - - #export "_thread_start" _thread_start - #export "_thread_exit" _thread_exit -} diff --git a/core/runtime/onyx_run.onyx b/core/runtime/onyx_run.onyx deleted file mode 100644 index 3a827aaf..00000000 --- a/core/runtime/onyx_run.onyx +++ /dev/null @@ -1,92 +0,0 @@ -package runtime - -#load "core/runtime/common" - -use core - -#local { - __stdout: os.File; - __stderr: os.File; - __stdin: os.File; -} - -__output_string :: (s: str) -> u32 { - err, wrote := io.stream_write(^__stdout, s); - return wrote; -} - -__output_error :: (s: str) -> u32 { - err, wrote := io.stream_write(^__stderr, s); - return wrote; -} - -__read_from_input :: (buffer: [] u8) -> i32 { - err, read := io.stream_read(^__stdin, buffer); - if err == .ReadPending do return 0; - if err != .None do return -1; - return read; -} - -#library "onyx_runtime" - -#foreign "onyx_runtime" { - __file_get_standard :: (fd: i32, out: ^fs.FileData) -> bool --- - - __args_get :: (argv: ^^u8, arg_buf: ^u8) -> void --- - __args_sizes_get :: (argc: ^i32, arg_buf_size: ^i32) -> void --- - - __exit :: (status: i32) -> void --- - - __sleep :: (milliseconds: i32) -> void --- - - __time :: () -> u64 --- -} - -#export "_start" () { - fd: fs.FileData; - __file_get_standard(1, ^fd); - __stdout = .{ - .{ ^fs.__file_stream_vtable }, - fd - }; - - __file_get_standard(2, ^fd); - __stderr = .{ - .{ ^fs.__file_stream_vtable }, - fd - }; - - __file_get_standard(0, ^fd); - __stdin = .{ - .{ ^fs.__file_stream_vtable, .Block_On_Read }, - fd - }; - - __runtime_initialize(); - context.thread_id = 0; - - #if (typeof (package main).main) == #type () -> void { - (package main).main(); - - } else { - args : [] cstr; - argv_buf_size : i32; - __args_sizes_get(^args.count, ^argv_buf_size); - - args = memory.make_slice(cstr, args.count); - argv_buf := cast(cstr) calloc(argv_buf_size); - __args_get(args.data, argv_buf); - - (package main).main(args); - } - - __flush_stdio(); -} - -#if Multi_Threading_Enabled { - __spawn_thread :: (id: i32, tls_base: rawptr, stack_base: rawptr, func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "onyx_runtime" "__spawn_thread" --- - __kill_thread :: (id: i32) -> i32 #foreign "onyx_runtime" "__kill_thread" --- - - #export "_thread_start" _thread_start - #export "_thread_exit" _thread_exit -} diff --git a/core/runtime/platform/js/platform.onyx b/core/runtime/platform/js/platform.onyx new file mode 100644 index 00000000..aa304e2a --- /dev/null +++ b/core/runtime/platform/js/platform.onyx @@ -0,0 +1,48 @@ +package runtime.platform + +use core +use runtime { + __runtime_initialize, + Multi_Threading_Enabled, + _thread_start, + _thread_exit +} + +// Platform supports +Supports_Files :: false +Supports_Directories :: false +Supports_Os :: false +Supports_Processes :: false +Supports_Time :: false +Supports_Networking :: false +Supports_Type_Info :: true +Supports_Threads :: true + +__output_string :: (s: str) -> u32 #foreign "host" "print_str" --- +__output_error :: (s: str) -> u32 #foreign "host" "print_str" --- +__exit :: (status: i32) -> void #foreign "host" "exit" --- +__read_from_input :: (buf: [] u8) -> u32 do return 0; + +// Sets up everything needed for execution. +__start :: () { + __runtime_initialize(); + context.thread_id = 0; + + #if (typeof (package main).main) == #type () -> void { + (package main).main(); + + } else { + args: [] cstr = .{ null, 0 }; + (package main).main(args); + } + + __flush_stdio(); +} + +#if Multi_Threading_Enabled { + __spawn_thread :: (id: i32, tls_base: rawptr, func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "host" "spawn_thread" --- + __kill_thread :: (id: i32) -> i32 #foreign "host" "kill_thread" --- + + #export "_thread_start" _thread_start + #export "_thread_exit" _thread_exit +} diff --git a/core/runtime/platform/onyx/fs.onyx b/core/runtime/platform/onyx/fs.onyx new file mode 100644 index 00000000..f122de60 --- /dev/null +++ b/core/runtime/platform/onyx/fs.onyx @@ -0,0 +1,117 @@ +package runtime.platform + +use core + +FileData :: #distinct i64 +DirectoryData :: #distinct u64 + +#local { + #foreign "onyx_runtime" { + __file_open_impl :: (path: str, mode: os.OpenMode, out_handle: ^FileData) -> os.FileError --- + + __file_close :: (fd: FileData) -> os.FileError --- + __file_stat :: (path: str, stat: ^os.FileStat) -> bool --- + __file_exists :: (path: str) -> bool --- + __file_remove :: (path: str) -> bool --- + __file_rename :: (old_path: str, new_path: str) -> bool --- + + __file_seek :: (handle: FileData, to: i32, whence: io.SeekFrom) -> i32 --- + __file_tell :: (handle: FileData) -> u32 --- + __file_read :: (handle: FileData, output_buffer: [] u8, bytes_read: ^u64) -> io.Error --- + __file_write :: (handle: FileData, input_buffer: [] u8, bytes_wrote: ^u64) -> io.Error --- + __file_flush :: (handle: FileData) -> io.Error --- + __file_size :: (handle: FileData) -> u32 --- + + __dir_open :: (path: str, dir: ^DirectoryData) -> bool --- + __dir_close :: (dir: DirectoryData) -> void --- + __dir_read :: (dir: DirectoryData, out_entry: ^os.DirectoryEntry) -> bool --- + __dir_create :: (path: str) -> bool --- + __dir_remove :: (path: str) -> bool --- + + + __enable_non_blocking_stdin :: () -> void --- + } +} + +__file_open :: (path: str, mode := os.OpenMode.Read) -> (FileData, os.FileError) { + handle: FileData; + error := __file_open_impl(path, mode, ^handle); + + return handle, error; +} + +// Export symbols from #foreign table +__file_close :: __file_close +__file_stat :: __file_stat +__file_exists :: __file_exists +__file_remove :: __file_remove +__file_rename :: __file_rename +__dir_open :: __dir_open +__dir_close :: __dir_close +__dir_read :: __dir_read +__dir_create :: __dir_create +__dir_remove :: __dir_remove + +__file_stream_vtable := io.Stream_Vtable.{ + seek = (use fs: ^os.File, to: i32, whence: io.SeekFrom) -> io.Error { + now := __file_seek(data, to, whence); + return (.None) if now >= 0 else .BadFile; + }, + + tell = (use fs: ^os.File) -> (io.Error, u32) { + return .None, __file_tell(data); + }, + + read = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { + bytes_read: u64; + error := __file_read(data, buffer, ^bytes_read); + return error, ~~bytes_read; + }, + + read_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { + __file_seek(data, at, .Start); + bytes_read: u64; + error := __file_read(data, buffer, ^bytes_read); + return error, ~~bytes_read; + }, + + read_byte = (use fs: ^os.File) -> (io.Error, u8) { + byte: u8; + error := __file_read(data, ~~ cast([1] u8) ^byte, null); + return error, byte; + }, + + write = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { + bytes_wrote: u64; + error := __file_write(data, buffer, ^bytes_wrote); + return error, ~~bytes_wrote; + }, + + write_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { + __file_seek(data, at, .Start); + bytes_wrote: u64; + error := __file_write(data, buffer, ^bytes_wrote); + return error, ~~bytes_wrote; + }, + + write_byte = (use fs: ^os.File, byte: u8) -> io.Error { + b := byte; + bytes_wrote: u64; + error := __file_write(data, .{ ^b, 1 }, ^bytes_wrote); + return error; + }, + + close = (use fs: ^os.File) -> io.Error { + __file_close(data); + return .None; + }, + + flush = (use fs: ^os.File) -> io.Error { + return __file_flush(data); + }, + + size = (use fs: ^os.File) -> i32 { + return __file_size(data); + }, +}; + diff --git a/core/runtime/platform/onyx/platform.onyx b/core/runtime/platform/onyx/platform.onyx new file mode 100644 index 00000000..f30c47bd --- /dev/null +++ b/core/runtime/platform/onyx/platform.onyx @@ -0,0 +1,128 @@ +package runtime.platform + +use core +use runtime { + __runtime_initialize, + Multi_Threading_Enabled, + _thread_start, + _thread_exit +} + +#load "./fs" + + +// Platform supports +Supports_Files :: true +Supports_Directories :: true +Supports_Os :: true +Supports_Processes :: true +Supports_Time :: true +Supports_Networking :: true +Supports_Type_Info :: true +Supports_Threads :: true + + +#library "onyx_runtime" + +#local { + __stdout: os.File; + __stderr: os.File; + __stdin: os.File; +} + +__output_string :: (s: str) -> u32 { + err, wrote := io.stream_write(^__stdout, s); + return wrote; +} + +__output_error :: (s: str) -> u32 { + err, wrote := io.stream_write(^__stderr, s); + return wrote; +} + +__read_from_input :: (buffer: [] u8) -> i32 { + err, read := io.stream_read(^__stdin, buffer); + if err == .ReadPending do return 0; + if err != .None do return -1; + return read; +} + + +ProcessData :: #distinct u64 + +#foreign "onyx_runtime" { + // Arguments + __args_get :: (argv: ^^u8, arg_buf: ^u8) -> void --- + __args_sizes_get :: (argc: ^i32, arg_buf_size: ^i32) -> void --- + + // OS + __exit :: (status: i32) -> void --- + __sleep :: (milliseconds: i32) -> void --- + + // Time and sleep + __time :: () -> u64 --- + __time_localtime :: (time: u64, tm: ^core.time.Timestamp) -> void --- + __time_gmtime :: (time: u64, tm: ^core.time.Timestamp) -> void --- + __time_mktime :: (tm: ^time.Timestamp) -> i64 --- + __time_strftime :: (buf: [] u8, format: cstr, tm: ^time.Timestamp) -> u32 --- + + // Processes + __process_spawn :: (path: str, args: [] str, non_blocking_io: bool, starting_directory: str) -> ProcessData --- + __process_read :: (handle: ProcessData, buffer: [] u8) -> i32 --- + __process_write :: (handle: ProcessData, buffer: [] u8) -> i32 --- + __process_kill :: (handle: ProcessData) -> bool --- + __process_wait :: (handle: ProcessData) -> os.ProcessResult --- + __process_destroy :: (handle: ProcessData) -> void --- + + // Misc + __file_get_standard :: (fd: i32, out: ^FileData) -> bool --- +} + +__start :: () { + fd: FileData; + __file_get_standard(1, ^fd); + __stdout = .{ + .{ ^__file_stream_vtable }, + fd + }; + + __file_get_standard(2, ^fd); + __stderr = .{ + .{ ^__file_stream_vtable }, + fd + }; + + __file_get_standard(0, ^fd); + __stdin = .{ + .{ ^__file_stream_vtable, .Block_On_Read }, + fd + }; + + __runtime_initialize(); + context.thread_id = 0; + + #if (typeof (package main).main) == #type () -> void { + (package main).main(); + + } else { + args : [] cstr; + argv_buf_size : i32; + __args_sizes_get(^args.count, ^argv_buf_size); + + args = memory.make_slice(cstr, args.count); + argv_buf := cast(cstr) calloc(argv_buf_size); + __args_get(args.data, argv_buf); + + (package main).main(args); + } + + __flush_stdio(); +} + +#if Multi_Threading_Enabled { + __spawn_thread :: (id: i32, tls_base: rawptr, stack_base: rawptr, func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "onyx_runtime" "__spawn_thread" --- + __kill_thread :: (id: i32) -> i32 #foreign "onyx_runtime" "__kill_thread" --- + + #export "_thread_start" _thread_start + #export "_thread_exit" _thread_exit +} diff --git a/core/runtime/platform/wasi/clock.onyx b/core/runtime/platform/wasi/clock.onyx new file mode 100644 index 00000000..468327f4 --- /dev/null +++ b/core/runtime/platform/wasi/clock.onyx @@ -0,0 +1,47 @@ +package core.clock + +#local runtime :: package runtime +#if runtime.runtime != .Wasi + && runtime.runtime != .Onyx { + #error "'core.clock' is only available with the 'wasi' or 'onyx' runtimes."; +} + +use package wasi + +time :: () -> u64 { + output_time: Timestamp; + clock_time_get(.Realtime, 500000, ^output_time); + return ~~(output_time / 1000000); +} + +time_ns :: () -> u64 { + output_time: Timestamp; + clock_time_get(.Realtime, 1, ^output_time); + return ~~output_time; +} + +sleep :: #match { + (seconds: u64) { sleep(nanoseconds=seconds * 1000000000); }, + (milliseconds: u64) { sleep(nanoseconds=milliseconds * 1000000); }, + + (nanoseconds: u64) { + tagged: SubscriptionTagged; + tagged.tag = .Clock; + tagged.clock = .{ + id = .Realtime, + timeout = cast(u64) nanoseconds, + precision = 1, + flags = ~~0, + }; + + subscription := Subscription.{ + userdata = 0, + u = tagged, + }; + + event: Event; + number_of_events: u32; + + error_code := poll_oneoff(^subscription, ^event, 1, ^number_of_events); + } +} diff --git a/core/runtime/platform/wasi/env.onyx b/core/runtime/platform/wasi/env.onyx new file mode 100644 index 00000000..7cd5b3bc --- /dev/null +++ b/core/runtime/platform/wasi/env.onyx @@ -0,0 +1,57 @@ +package core.env + +#local runtime :: package runtime +#if runtime.runtime != .Wasi + && runtime.runtime != .Onyx { + #error "'core.env' is only available with the 'wasi' and 'onyx' runtimes."; +} + + +use package wasi { environ_get, environ_sizes_get, Size } +#package map :: package core.map +#package memory :: package core.memory +#package string :: package core.string + +Environment :: struct { + vars : Map(str, str); + + buffer : [] u8; + buffer_allocator : Allocator; +} + +get_env :: (allocator := context.allocator) -> Environment { + env_count, env_buf_size : Size; + environ_sizes_get(^env_count, ^env_buf_size); + + env_var := memory.make_slice(cstr, env_count, allocator=allocator); + env_buf := memory.make_slice(u8, env_buf_size, allocator=allocator); + + environ_get(env_var.data, env_buf.data); + + while i := cast(i32) (env_var.count - 1); i >= 0 { + defer i -= 1; + + env_var[i] = cast(cstr) (cast(^u32) env_var.data)[i]; + } + + env_map := map.make(str, str, ""); + for env: env_var { + s := string.from_cstr(env); + var := string.read_until(^s, #char "="); + map.put(^env_map, var, string.advance(s, 1)); + } + + raw_free(allocator, env_var.data); + + return .{ + vars = env_map, + buffer = env_buf, + buffer_allocator = allocator, + }; +} + +free_env :: (use env: ^Environment) { + map.free(^vars); + + raw_free(buffer_allocator, buffer.data); +} diff --git a/core/runtime/platform/wasi/platform.onyx b/core/runtime/platform/wasi/platform.onyx new file mode 100644 index 00000000..4e01eb0a --- /dev/null +++ b/core/runtime/platform/wasi/platform.onyx @@ -0,0 +1,112 @@ +package runtime.platform + +#load "./wasi_defs" +#load "./wasi_fs" + +use core +use wasi { + IOVec, SubscriptionTagged, Subscription, Event, Size, + poll_oneoff, fd_write, fd_datasync, fd_read, + args_get, args_sizes_get, + proc_exit +} +use runtime { + __runtime_initialize, + Multi_Threading_Enabled, +} + + +// Platform supports +Supports_Files :: true +Supports_Directories :: true +Supports_Os :: true +Supports_Processes :: false +Supports_Time :: false +Supports_Networking :: false +Supports_Type_Info :: true +Supports_Threads :: false + + + +__output_string :: (s: str) -> u32 { + STDOUT_FILENO :: 1 + + vec := IOVec.{ buf = cast(u32) s.data, len = s.count }; + tmp : Size; + fd_write(STDOUT_FILENO, ^vec, 1, ^tmp); + fd_datasync(STDOUT_FILENO); + return tmp; +} + +__exit :: (status: i32) do proc_exit(status); + +__read_from_input :: (buffer: [] u8) -> i32 { + STDIN_FILENO :: 0 + + vec := IOVec.{ buf = cast(i32) buffer.data, len = buffer.count }; + read: Size; + error := fd_read(STDIN_FILENO, ^vec, 1, ^read); + if error != .Success do return -1; + + return read; +} + +__sleep :: (milliseconds: u32) { + tagged: SubscriptionTagged; + tagged.tag = .Clock; + tagged.clock = .{ + id = .Realtime, + timeout = cast(u64) milliseconds * 1000000, + precision = 1, + flags = ~~0, + }; + + subscription := Subscription.{ + userdata = 0, + u = tagged, + }; + + event: Event; + number_of_events: u32; + + error_code := poll_oneoff(^subscription, ^event, 1, ^number_of_events); +} + + +// Sets up everything needed for execution. +__start :: () { + __runtime_initialize(); + context.thread_id = 0; + + #if (typeof (package main).main) == #type () -> void { + (package main).main(); + + } else { + args : [] cstr; + argv_buf_size : Size; + args_sizes_get(^args.count, ^argv_buf_size); + + args = memory.make_slice(cstr, args.count); + argv_buf := cast(cstr) calloc(argv_buf_size); + args_get(args.data, argv_buf); + + + // This post processing of the argv array needs to happen if the target is using + // 32-bit pointers, instead of 64-bits. Right now, Onyx pointers take up 64-bits, + // but in most circumstances, only the lower 32-bits are used. When webassembly + // standardizes the 64-bit address space, it will be an easy conversion over. + // But for right now, WASI will give the argv array 32-bit pointers, instead of + // 64-bit pointers. This loops expands the 32-bit pointers into 64-bit pointers + // while not clobbering any of them. + while i := cast(i32) (args.count - 1); i >= 0 { + defer i -= 1; + + args[i] = cast(cstr) (cast(^u32) args.data)[i]; + } + + (package main).main(args); + } + + __flush_stdio(); +} + diff --git a/core/runtime/platform/wasi/wasi_defs.onyx b/core/runtime/platform/wasi/wasi_defs.onyx new file mode 100644 index 00000000..73505639 --- /dev/null +++ b/core/runtime/platform/wasi/wasi_defs.onyx @@ -0,0 +1,435 @@ +package wasi + +Size :: #type u32; +Filesize :: #type u64; +Timestamp :: #type u64; + +ClockID :: enum (u32) { + Realtime :: 0x00; + Monotonic :: 0x01; + ProcessCPUTimeID :: 0x02; + ThreadCPUTimeID :: 0x03; +} + +Errno :: enum (u16) { + Success :: 0x00; + TooBig :: 0x01; + Access :: 0x02; + AddrInUse :: 0x03; + AddrNotAvail :: 0x04; + AFNoSupport :: 0x05; + Again :: 0x06; + Already :: 0x07; + BadFile :: 0x08; + BadMsg :: 0x09; + Busy :: 0x0a; + Canceled :: 0x0b; + Child :: 0x0c; + ConnAborted :: 0x0d; + ConnRefused :: 0x0e; + ConnReset :: 0x0f; + DeadLock :: 0x10; + DestAddrReq :: 0x11; + Domain :: 0x12; + DQUOT :: 0x13; + Exist :: 0x14; + Fault :: 0x15; + FileTooBig :: 0x16; + HostUnreach :: 0x17; + IdentRemoved :: 0x18; + IllegalSeq :: 0x19; + InProgress :: 0x1a; + Interrupt :: 0x1b; + Invalid :: 0x1c; + IO :: 0x1d; + IsConnection :: 0x1e; + IsDirectory :: 0x1f; + Loop :: 0x20; + MFile :: 0x21; + MLink :: 0x22; + MsgSize :: 0x23; + MultiHop :: 0x24; + NameTooLong :: 0x25; + NetDown :: 0x26; + NetReset :: 0x27; + NetUnreach :: 0x28; + NFile :: 0x29; + NoBufs :: 0x2a; + NoDev :: 0x2b; + NoEntry :: 0x2c; + NoExec :: 0x2d; + NoLock :: 0x2e; + NoLink :: 0x2f; + NoMemory :: 0x30; + NoMsg :: 0x31; + NoProtoOpt :: 0x32; + NoSpace :: 0x33; + NoSys :: 0x34; + NotConn :: 0x35; + NotDir :: 0x36; + NotEmpty :: 0x37; + NotRecover :: 0x38; + NotSock :: 0x39; + NotSupported :: 0x3a; + NoTTY :: 0x3b; + NXIO :: 0x3c; + Overflow :: 0x3d; + OwnerDead :: 0x3e; + Permission :: 0x3f; + Pipe :: 0x40; + Protocol :: 0x41; + ProtoNoSup :: 0x42; + Prototype :: 0x43; + Range :: 0x44; + ReadonlyFS :: 0x45; + SeekPipe :: 0x46; + Search :: 0x47; + Stale :: 0x48; + Timedout :: 0x49; + TextBusy :: 0x4a; + XDev :: 0x4b; + + NotCapable :: 0x4c; +} + +Rights :: enum #flags (u64) { + DataSync; + Read; + Seek; + FdStatSetFlags; + Sync; + Tell; + Write; + Advise; + Allocate; + PathCreateDirectory; + PathCreateFile; + PathLinkSource; + PathLinkTarget; + PathOpen; + ReadDir; + PathReadlink; + PathRenameSource; + PathRenameTarget; + PathFilestatGet; + PathFilestateSetSize; + PathFilestateSetTimes; + FilestatGet; + FilestatSetSize; + FilestatSetTimes; + PathSymlink; + PathRemoveDirectory; + PathUnlinkFile; + PollFDReadWrite; + SockShutdown; +} + +FileDescriptor :: #type i32; + +IOVec :: struct { + buf : u32; // actually a ^u8, but WASM is 32-bit at the moment; + len : u32; +} + +CIOVec :: #type IOVec; // Constant IOVec + +FileDelta :: #type i64; + +Whence :: enum (u8) { + Set :: 0x00; + Cur :: 0x01; + End :: 0x02; +} + +DirCookie :: #type u64; +DirNameLen :: #type u32; +INode :: #type u64; + +Filetype :: enum (u8) { + Unknown :: 0x00; + BlockDevice :: 0x01; + CharDevice :: 0x02; + Directory :: 0x03; + RegularFile :: 0x04; + SocketDgram :: 0x05; + SocketStream :: 0x06; + SymLink :: 0x07; +} + +DirEnt :: struct { + d_next : DirCookie; + d_ino : INode; + d_namlen : DirNameLen; + d_type : Filetype; +} + +Advice :: enum (u8) { + Normal :: 0x00; + Sequential :: 0x01; + Random :: 0x02; + WillNeed :: 0x03; + DontNeed :: 0x04; + NoReuse :: 0x05; +} + +FDFlags :: enum #flags (u16) { + Append; + DSync; + NonBlock; + RSync; + Sync; +} + +FDStat :: struct { + fs_filetype : Filetype; + fs_flags : FDFlags; + fs_rights_base : Rights; + fs_rights_inheriting : Rights; +} + +Device :: #type u64; + +FSTFlags :: enum #flags (u16) { + ATIM; + ATIM_NOW; + MTIM; + MTIM_NOW; +} + +LookupFlags :: enum #flags (u32) { + SymLinkFollow; +} + +OFlags :: enum #flags (u16) { + Creat; + Directory; + FailIfExists; + Trunc; +} + +LinkCount :: #type u64; + +FileStat :: struct { + dev : Device; + ino : INode; + filetype : Filetype; + + // ANGER(Brendan Hansen): I give the worst documentation in + // the entire world award to WASI, whose best documentation + // is a C header file, that is itself incorrect. nlink does + // not exist in the current version of wasmtime, and should + // not be here, but maybe in the future? Ugh. + // + // STILL ANGERY(Brendan Hansen): Actually, nlink does exist + // in the current version of wasmtime; however its actually + // a 32-bit value, not 64-bit, which means the offsets were + // incorrect. I fixed it for now, but when wasmtime updates + // the size of 'LinkCount' needs to be changed. + // + // EVEN ANGRIER(Brendan Hansen): ACTUALLY, I realized today + // that it has changed (at least for Wasmer). There was not + // any mention of this on any release documents, but hey, I + // guess this can actually be 64-bits now. + // - brendanfh 2020/12/05 + // - brendanfh 2020/12/07 + // - brendanfh 2021/07/10 + nlink : LinkCount; + + size : Filesize; + atim : Timestamp; + mtim : Timestamp; + ctim : Timestamp; +} + +Userdata :: #type u64; + +EventType :: enum (u8) { + Clock; + FDRead; + FDWrite; +} + +EventRWFlags :: enum #flags (u16) { + ReadWriteHangUp; +} + +EventFDReadWrite :: struct { + nbytes : Filesize; + flags : EventRWFlags; +} + +Event :: struct { + userdata : Userdata; + error : Errno; + type : EventType; + fd_readwrite : EventFDReadWrite; +} + +SubClockFlags :: enum #flags (u16) { + ClockAbsTime; +} + +SubscriptionClock :: struct { + id : ClockID; + timeout : Timestamp; + precision : Timestamp; + flags : SubClockFlags; +} + +SubscriptionFDReadWrite :: struct { + file_descriptor : FileDescriptor; +} + +SubscriptionTagged :: struct { + tag : EventType; + + use u : struct #union { + clock : SubscriptionClock; + fd_read : SubscriptionFDReadWrite; + fd_write : SubscriptionFDReadWrite; + }; +} + +Subscription :: struct { + userdata : Userdata; + + u : SubscriptionTagged; +} + +ExitCode :: #type u32; +Signal :: enum (u8) { + None; + Hup; + Int; + Quit; + Ill; + Trap; + Abrt; + Bus; + Fpe; + Kill; + USR1; + Segv; + USR2; + Pipe; + Alrm; + Term; + Chld; + Stop; + Tstp; + Ttin; + Urg; + Xcpu; + Xfsz; + Vtalrm; + Prof; + Winch; + Poll; + Pwr; + Sys; +} + +RIFlags :: enum #flags (u16) { + RecvPeek; + RecvWaitAll; +} + +ROFlags :: enum #flags (u16) { + RecvDataTruncated :: 1; +} + +SIFlags :: enum #flags (u16) { + None; +} + +SDFlags :: enum #flags (u16) { + RD; + WR; +} + +PreopenType :: enum (u8) { + Dir :: 0x00; +} + +PrestatDir :: struct { + pr_name_len : Size; +} + +PrestatTagged :: struct { + tag : PreopenType; + + u : struct #union { + dir : PrestatDir; + }; +} + + +// FUNCTIONS +args_get :: (argv: ^^u8, argv_buf: ^u8) -> Errno #foreign "wasi_snapshot_preview1" "args_get"--- +args_sizes_get :: (argc: ^Size, argv_buf_size: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "args_sizes_get" --- + +environ_get :: (environ: ^^u8, environ_buf: ^u8) -> Errno #foreign "wasi_snapshot_preview1" "environ_get" --- +environ_sizes_get :: (environc: ^Size, environ_buf_size: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "environ_sizes_get" --- + +clock_res_get :: (id: ClockID, resolution: ^Timestamp) -> Errno #foreign "wasi_snapshot_preview1" "clock_res_get" --- +clock_time_get :: (id: ClockID, precision: Timestamp, time: ^Timestamp) -> Errno #foreign "wasi_snapshot_preview1" "clock_time_get" --- + +fd_advise :: (fd: FileDescriptor, offset: Filesize, len: Filesize, advice: Advice) -> Errno #foreign "wasi_snapshot_preview1" "fd_advise" --- +fd_allocate :: (fd: FileDescriptor, offset: Filesize, len: Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_allocate" --- +fd_close :: (fd: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_close" --- +fd_datasync :: (fd: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_datasync" --- +fd_fdstat_get :: (fd: FileDescriptor, stat: ^FDStat) -> Errno #foreign "wasi_snapshot_preview1" "fd_fdstat_get" --- +fd_fdstat_set_flags :: (fd: FileDescriptor, flags: FDFlags) -> Errno #foreign "wasi_snapshot_preview1" "fd_fdstat_set_flags" --- +fd_fdstat_set_rights :: (fd: FileDescriptor, rights_base: Rights, rights_inheriting: Rights) -> Errno #foreign "wasi_snapshot_preview1" "fd_fdstat_set_rights" --- +fd_filestat_get :: (fd: FileDescriptor, buf: ^FileStat) -> Errno #foreign "wasi_snapshot_preview1" "fd_filestat_get" --- +fd_filestat_set_size :: (fd: FileDescriptor, size: Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_filestat_set_size" --- +fd_filestat_set_times :: (fd: FileDescriptor, atim: Timestamp, mtim: Timestamp, fst_flags: FSTFlags) -> Errno #foreign "wasi_snapshot_preview1" "fd_filestat_set_times" --- +fd_pread :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, offset: Filesize, nread: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_pread" --- +fd_prestat_get :: (fd: FileDescriptor, buf: ^PrestatTagged) -> Errno #foreign "wasi_snapshot_preview1" "fd_prestat_get" --- +fd_prestat_dir_name :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "fd_prestat_dir_name" --- +fd_pwrite :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, offset: Filesize, nwritten: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_pwrite" --- +fd_read :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, nread: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_read" --- +fd_readdir :: (fd: FileDescriptor, buf: ^u8, buf_len: Size, cookie: DirCookie, bufused: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_readdir" --- +fd_renumber :: (fd: FileDescriptor, to: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_renumber" --- +fd_seek :: (fd: FileDescriptor, offset: FileDelta, whence: Whence, newoffset: ^Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_seek" --- +fd_sync :: (fd: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_sync" --- +fd_tell :: (fd: FileDescriptor, offset: ^Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_tell" --- +fd_write :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, nwritten: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_write" --- + +path_create_directory :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_create_directory" --- +path_filestat_get :: (fd: FileDescriptor, flags: LookupFlags, path: str, buf: ^FileStat) -> Errno #foreign "wasi_snapshot_preview1" "path_filestat_get" --- +path_filestat_set_times :: (fd: FileDescriptor, flags: LookupFlags, path: str, atim: Timestamp, mtim: Timestamp, fst_flags: FSTFlags) -> Errno #foreign "wasi_snapshot_preview1" "path_filestat_set_times" --- + +path_link :: (fd: FileDescriptor, old_flags: LookupFlags, old_path: str, new_fd: FileDescriptor, new_path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_link" --- +path_open :: (fd: FileDescriptor + , dirflags: LookupFlags + , path: str + , oflags: OFlags + , fs_rights_base: Rights + , fs_rights_inherting: Rights + , fdflags: FDFlags + , opened_fd: ^FileDescriptor + ) -> Errno + #foreign "wasi_snapshot_preview1" "path_open" --- + +path_readlink :: (fd: FileDescriptor, path: str, buf: ^u8, buf_len: Size, bufused: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "path_readlink" --- +path_remove_directory :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_remove_directory" --- +path_rename :: (fd: FileDescriptor, old_path: str, new_fd: FileDescriptor, new_path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_rename" --- +path_symlink :: (old_path: ^u8, old_path_len: Size, fd: FileDescriptor, new_path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_symlink" --- +path_unlink_file :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_unlink_file" --- + +poll_oneoff :: (in: ^Subscription, out: ^Event, nsubscriptions: Size, nevents: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "poll_oneoff" --- + +proc_exit :: (rval: ExitCode) -> void #foreign "wasi_snapshot_preview1" "proc_exit" --- +proc_raise :: (sig: Signal) -> Errno #foreign "wasi_snapshot_preview1" "proc_raise" --- + +sched_yield :: () -> Errno #foreign "wasi_snapshot_preview1" "sched_yield" --- + +random_get :: (buf: ^u8, buf_len: Size) -> Errno #foreign "wasi_snapshot_preview1" "random_get" --- + +sock_recv :: (fd: FileDescriptor, ri_data: ^IOVec, ri_data_len: Size, ri_flags: RIFlags, ro_datalen: ^Size, ro_flags: ^ROFlags) -> Errno #foreign "wasi_snapshot_preview1" "sock_recv" --- +sock_send :: (fd: FileDescriptor, si_data: ^IOVec, ri_data_len: Size, si_flags: SIFlags, so_datalen: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "sock_send" --- +sock_shutdown :: (fd: FileDescriptor, how: SDFlags) -> Errno #foreign "wasi_snapshot_preview1" "sock_shutdown" --- + + + diff --git a/core/runtime/platform/wasi/wasi_fs.onyx b/core/runtime/platform/wasi/wasi_fs.onyx new file mode 100644 index 00000000..8e427280 --- /dev/null +++ b/core/runtime/platform/wasi/wasi_fs.onyx @@ -0,0 +1,336 @@ +package runtime.platform + +#local runtime :: package runtime +#if runtime.runtime != .Wasi { + #error "The file system library is currently only available on the WASI runtime, and should only be included if that is the chosen runtime." +} + +use package core + +#local wasi :: package wasi +use package wasi { + FileDescriptor, + FDFlags, OFlags, Rights, + LookupFlags, Errno, + IOVec, Size, + FileStat, Whence +} + +FileData :: struct { + fd : FileDescriptor = -1; + + mode : os.OpenMode = .Invalid; + rights : Rights = ~~ 0; + flags : FDFlags = ~~ 0; +} + +__file_open :: (path: str, mode := os.OpenMode.Read) -> (FileData, os.FileError) { + // Requesting all of the rights because why not. + rights := + Rights.DataSync + | Rights.Read + | Rights.Seek + | Rights.FdStatSetFlags + | Rights.Sync + | Rights.Tell + | Rights.Write + | Rights.Advise + | Rights.Allocate + | Rights.PathCreateDirectory + | Rights.PathCreateFile + | Rights.PathLinkSource + | Rights.PathLinkTarget + | Rights.PathOpen + | Rights.ReadDir + | Rights.PathReadlink + | Rights.PathRenameSource + | Rights.PathRenameTarget + | Rights.PathFilestatGet + | Rights.PathFilestateSetSize + | Rights.PathFilestateSetTimes + | Rights.FilestatGet + | Rights.FilestatSetSize + | Rights.FilestatSetTimes + | Rights.PathSymlink + | Rights.PathRemoveDirectory + | Rights.PathUnlinkFile + | Rights.PollFDReadWrite; + + open_flags := cast(OFlags) 0; + fd_flags := FDFlags.Sync; + + switch mode { + case .Write { + open_flags |= OFlags.Creat | OFlags.Trunc; + rights |= Rights.Write | Rights.Seek | Rights.Tell; + } + + case .Append { + open_flags |= OFlags.Creat; + rights |= Rights.Write | Rights.Seek | Rights.Tell; + fd_flags |= FDFlags.Append; + } + + case .Read { + rights |= Rights.Read | Rights.Seek | Rights.Tell; + } + } + + file := FileData.{ fd = -1 }; + file.mode = mode; + file.rights = rights; + file.flags = fd_flags; + + // Currently the directory's file descriptor appears to always be 3 + // However, this is not necessarily correct, so also try a preopened directory + for DIR_FD: .[ 3, 4 ] { + if err := wasi.path_open( + DIR_FD, + .SymLinkFollow, + path, + open_flags, + rights, + rights, + fd_flags, + ^file.fd); + err == .Success { + return file, .None; + } + } + + // @TODO // provide a better error code. + return file, .NotFound; +} + +__file_close :: (file: FileData) -> os.FileError { + if wasi.fd_close(file.fd) != .Success { + return .BadFile; + } + + return .None; +} + +__file_stat :: (path: str, out: ^os.FileStat) -> bool { + fs: wasi.FileStat; + + exists := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_filestat_get(it, .SymLinkFollow, path, ^fs); + if err == .Success { + exists = true; + out.size = ~~ fs.size; + + switch fs.filetype { + case .RegularFile do out.type = .RegularFile; + case .Directory do out.type = .Directory; + case .SymLink do out.type = .SymLink; + case #default do out.type = .Unknown; + } + } + } + + return exists; +} + +__file_exists :: (path: str) -> bool { + fs: wasi.FileStat; + + exists := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_filestat_get(it, .SymLinkFollow, path, ^fs); + if err == .Success do exists = true; + } + + return exists; +} + +__file_remove :: (path: str) -> bool { + removed := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_unlink_file(it, path); + if err == .Success do removed = true; + } + + return removed; +} + +__file_rename :: (old_path: str, new_path: str) -> bool { + renamed := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_rename(it, old_path, it, new_path); + if err == .Success do renamed = true; + } + + return renamed; +} + +__file_stream_vtable := io.Stream_Vtable.{ + seek = (use fs: ^os.File, to: i32, whence: io.SeekFrom) -> io.Error { + // Currently, the new offset is just ignored. + newoffset : wasi.Filesize; + error := wasi.fd_seek(data.fd, ~~ to, ~~ whence, ^newoffset); + if error != .Success do return .BadFile; + + return .None; + }, + + tell = (use fs: ^os.File) -> (io.Error, u32) { + location : wasi.Filesize; + error := wasi.fd_tell(data.fd, ^location); + if error != .Success do return .BadFile, 0; + + return .None, ~~location; + }, + + read = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { + bytes_read : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_read(data.fd, ^vec, 1, ^bytes_read); + if error != .Success do return .BadFile, 0; + + return .None, bytes_read; + }, + + read_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { + bytes_read : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_pread(data.fd, ^vec, 1, ~~at, ^bytes_read); + + // FIX: Maybe report io.Error.OutOfBounds if the 'at' was out of bounds? + if error != .Success do return .BadFile, 0; + + return .None, bytes_read; + }, + + read_byte = (use fs: ^os.File) -> (io.Error, u8) { + bytes_read : wasi.Size; + byte : u8; + vec := IOVec.{ buf = cast(u32) ^byte, len = 1}; + error := wasi.fd_read(data.fd, ^vec, 1, ^bytes_read); + if error != .Success do return .BadFile, 0; + + return .None, byte; + }, + + write = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { + bytes_written : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_write(data.fd, ^vec, 1, ^bytes_written); + if error != .Success do return .BadFile, 0; + + return .None, bytes_written; + }, + + write_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { + bytes_written : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_pwrite(data.fd, ^vec, 1, ~~at, ^bytes_written); + + // FIX: Maybe report io.Error.OutOfBounds if the 'at' was out of bounds? + if error != .Success do return .BadFile, 0; + + return .None, bytes_written; + }, + + write_byte = (use fs: ^os.File, byte: u8) -> io.Error { + bytes_written : wasi.Size; + byte_to_write := byte; + vec := IOVec.{ buf = cast(u32) ^byte_to_write, len = 1 }; + error := wasi.fd_write(data.fd, ^vec, 1, ^bytes_written); + if error != .Success do return .BadFile; + + return .None; + }, + + close = (use fs: ^os.File) -> io.Error { + __file_close(data); + return .None; + }, + + flush = (use fs: ^os.File) -> io.Error { + wasi.fd_datasync(data.fd); + return .None; + }, + + size = (use fs: ^os.File) -> i32 { + file_stat: FileStat; + if wasi.fd_filestat_get(data.fd, ^file_stat) != .Success do return 0; + + return ~~ file_stat.size; + }, +} + + +WasiDirectory :: struct { + dir_fd: FileDescriptor; + last_cookie: wasi.DirCookie; +} + +DirectoryData :: ^WasiDirectory; + +__dir_open :: (path: str, dir: ^DirectoryData) -> bool { + dir_fd: FileDescriptor; + err := wasi.path_open(4, .SymLinkFollow, path, .Directory, ~~0xffffffff, ~~0xffffffff, .Sync, ^dir_fd); + if err != .Success { + return false; + } + + d := new(WasiDirectory); + d.dir_fd = dir_fd; + d.last_cookie = 0; + + *dir = d; + return true; +} + +__dir_close :: (dir: DirectoryData) { + wasi.fd_close(dir.dir_fd); + cfree(dir); +} + +__dir_read :: (dir: DirectoryData, out_entry: ^os.DirectoryEntry) -> bool { + buffer: [512] u8; + bufused: u32; + + err := wasi.fd_readdir(dir.dir_fd, ~~buffer, 512, dir.last_cookie, ^bufused); + if err != .Success || bufused == 0 do return false; + + dirent := cast(^wasi.DirEnt) buffer; + switch dirent.d_type { + case .Unknown do out_entry.type = .Unknown; + case .BlockDevice do out_entry.type = .Block; + case .CharDevice do out_entry.type = .Char; + case .Directory do out_entry.type = .Directory; + case .RegularFile do out_entry.type = .RegularFile; + case .SymLink do out_entry.type = .SymLink; + case #default do out_entry.type = .Other; + } + + out_entry.identifier = ~~dirent.d_ino; + out_entry.name_length = dirent.d_namlen; + memory.set(~~^out_entry.name_data, 0, 256); + memory.copy(~~^out_entry.name_data, ~~(dirent + 1), math.min(dirent.d_namlen, sizeof typeof out_entry.name_data)); + + dir.last_cookie = dirent.d_next; + return true; +} + +__dir_create :: (path: str) -> bool { + created := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_create_directory(it, path); + if err == .Success do created = true; + } + + return created; +} + +__dir_remove :: (path: str) -> bool { + removed := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_remove_directory(it, path); + if err == .Success do removed = true; + } + + return removed; +} diff --git a/core/runtime/wasi.onyx b/core/runtime/wasi.onyx deleted file mode 100644 index f94874de..00000000 --- a/core/runtime/wasi.onyx +++ /dev/null @@ -1,91 +0,0 @@ -package runtime - -#load "core/wasi/wasi" -#load "core/runtime/common" - -use wasi -use core - -__output_string :: (s: str) -> u32 { - STDOUT_FILENO :: 1 - - vec := IOVec.{ buf = cast(u32) s.data, len = s.count }; - tmp : Size; - fd_write(STDOUT_FILENO, ^vec, 1, ^tmp); - fd_datasync(STDOUT_FILENO); - return tmp; -} - -__exit :: (status: i32) do proc_exit(status); - -__read_from_input :: (buffer: [] u8) -> i32 { - STDIN_FILENO :: 0 - - vec := IOVec.{ buf = cast(i32) buffer.data, len = buffer.count }; - read: Size; - error := fd_read(STDIN_FILENO, ^vec, 1, ^read); - if error != .Success do return -1; - - return read; -} - -__sleep :: (milliseconds: u32) { - tagged: SubscriptionTagged; - tagged.tag = .Clock; - tagged.clock = .{ - id = .Realtime, - timeout = cast(u64) milliseconds * 1000000, - precision = 1, - flags = ~~0, - }; - - subscription := Subscription.{ - userdata = 0, - u = tagged, - }; - - event: Event; - number_of_events: u32; - - error_code := poll_oneoff(^subscription, ^event, 1, ^number_of_events); -} - - -// The builtin _start proc. -// Sets up everything needed for execution. -#export "_start" () { - __runtime_initialize(); - context.thread_id = 0; - - #if (typeof (package main).main) == #type () -> void { - (package main).main(); - - } else { - args : [] cstr; - argv_buf_size : Size; - args_sizes_get(^args.count, ^argv_buf_size); - - args = memory.make_slice(cstr, args.count); - argv_buf := cast(cstr) calloc(argv_buf_size); - args_get(args.data, argv_buf); - - - // This post processing of the argv array needs to happen if the target is using - // 32-bit pointers, instead of 64-bits. Right now, Onyx pointers take up 64-bits, - // but in most circumstances, only the lower 32-bits are used. When webassembly - // standardizes the 64-bit address space, it will be an easy conversion over. - // But for right now, WASI will give the argv array 32-bit pointers, instead of - // 64-bit pointers. This loops expands the 32-bit pointers into 64-bit pointers - // while not clobbering any of them. - while i := cast(i32) (args.count - 1); i >= 0 { - defer i -= 1; - - args[i] = cast(cstr) (cast(^u32) args.data)[i]; - } - - (package main).main(args); - } - - __flush_stdio(); -} - diff --git a/core/std.onyx b/core/std.onyx index 2df668c5..fc938665 100644 --- a/core/std.onyx +++ b/core/std.onyx @@ -49,42 +49,45 @@ package core #load "./misc/arg_parse" #load "./misc/method_ops" -#if runtime.runtime == .Wasi || runtime.runtime == .Onyx { +#load "./time/date" +#load "./encoding/base64" + +#load "./runtime/common" + +#if runtime.platform.Supports_Files { #load "./os/file" - #load "./os/os" +} + +#if runtime.platform.Supports_Directories { #load "./os/dir" } -#if runtime.runtime == .Onyx { - #load "./runtime/onyx_run" +#if runtime.platform.Supports_Os { + #load "./os/os" +} +#if runtime.platform.Supports_Processes { #load "./os/process" +} + +#if runtime.platform.Supports_Time { #load "./time/time" - #load "./time/date" +} +#if runtime.platform.Supports_Networking { #load "./net/net" #load "./net/tcp" - - #load "./onyx/fs" - #load "./onyx/cptr" - #load "./onyx/cbindgen" - #load "./onyx/fault_handling" -} -#if runtime.runtime == .Wasi { - #load "./wasi/wasi" - #load "./runtime/wasi" - #load "./wasi/clock" - #load "./wasi/env" - #load "./wasi/wasi_fs" } -#if runtime.runtime == .Js { #load "./runtime/js" } -#if runtime.runtime != .Custom { + +#if runtime.platform.Supports_Type_Info { #load "./runtime/info/helper" #load "./io/stdio" - - #load "./encoding/base64" - #load "./encoding/csv" #load "./misc/any_utils" + #load "./encoding/csv" +} + +#if runtime.platform.Supports_Threads && runtime.Multi_Threading_Enabled { + #load "./threads/thread" } #if runtime.Multi_Threading_Enabled { @@ -95,6 +98,25 @@ package core #load "./sync/semaphore" #load "./sync/barrier" #load "./sync/once" +} - #load "./threads/thread" + +// +// Load platform files +// + +#if runtime.runtime == .Onyx { + #load "./runtime/platform/onyx/platform" + + #load "./onyx/cptr" + #load "./onyx/cbindgen" + #load "./onyx/fault_handling" } + +#if runtime.runtime == .Wasi { + #load "./runtime/platform/wasi/platform" +} + +#if runtime.runtime == .Js { + #load "./runtime/platform/js/platform" +} \ No newline at end of file diff --git a/core/threads/thread.onyx b/core/threads/thread.onyx index c234a834..c7152f16 100644 --- a/core/threads/thread.onyx +++ b/core/threads/thread.onyx @@ -41,7 +41,7 @@ spawn :: (t: ^Thread, data: ^$T, func: (^T) -> void) { stack_base := raw_alloc(alloc.heap_allocator, 1 << 20); - runtime.__spawn_thread(t.id, tls_base, stack_base, func, data); + runtime.platform.__spawn_thread(t.id, tls_base, stack_base, func, data); } // @@ -54,7 +54,7 @@ join :: (t: ^Thread) { __atomic_wait(^t.id, t.id); } else { // To not completely kill the CPU. - runtime.__sleep(1); + runtime.platform.__sleep(1); } } } @@ -65,7 +65,7 @@ join :: (t: ^Thread) { kill :: (t: ^Thread) -> i32 { if !t.alive do return -1; - ret := runtime.__kill_thread(t.id); + ret := runtime.platform.__kill_thread(t.id); if ret > 0 do __exited(t.id); return 1; diff --git a/core/time/date.onyx b/core/time/date.onyx index 74bc8249..42944419 100644 --- a/core/time/date.onyx +++ b/core/time/date.onyx @@ -16,8 +16,11 @@ Date :: struct { return .{ year, month - 1, day }; } - today :: () -> Date { - return now()->as_date(); + + #if runtime.platform.Supports_Time { + today :: () -> Date { + return now()->as_date(); + } } add_months :: (d: Date, days: i32) -> Date { diff --git a/core/time/time.onyx b/core/time/time.onyx index b61975d0..5d4ad95f 100644 --- a/core/time/time.onyx +++ b/core/time/time.onyx @@ -1,8 +1,14 @@ package core.time use core {os, conv} +use runtime.platform { + __time_gmtime, + __time_localtime, + __time_mktime, + __time_strftime +} -#if runtime.runtime != .Onyx { +#if !runtime.platform.Supports_Time { #error "'core.time' should only be used with the Onyx runtime, for now."; } @@ -328,12 +334,3 @@ strptime :: (buf_: [] u8, format_: [] u8, tm: ^Timestamp) -> bool { } } -#local { - #foreign "onyx_runtime" { - __time_localtime :: (time: u64, tm: ^Timestamp) -> void --- - __time_gmtime :: (time: u64, tm: ^Timestamp) -> void --- - __time_mktime :: (tm: ^Timestamp) -> i64 --- - __time_strftime :: (buf: [] u8, format: cstr, tm: ^Timestamp) -> u32 --- - } -} - diff --git a/core/wasi/README.md b/core/wasi/README.md deleted file mode 100644 index 3c3e6ee2..00000000 --- a/core/wasi/README.md +++ /dev/null @@ -1 +0,0 @@ -These files are only available on the 'wasi' runtime. In a C compilation, other files will be included, and the API between them will have to remain the same. \ No newline at end of file diff --git a/core/wasi/clock.onyx b/core/wasi/clock.onyx deleted file mode 100644 index 468327f4..00000000 --- a/core/wasi/clock.onyx +++ /dev/null @@ -1,47 +0,0 @@ -package core.clock - -#local runtime :: package runtime -#if runtime.runtime != .Wasi - && runtime.runtime != .Onyx { - #error "'core.clock' is only available with the 'wasi' or 'onyx' runtimes."; -} - -use package wasi - -time :: () -> u64 { - output_time: Timestamp; - clock_time_get(.Realtime, 500000, ^output_time); - return ~~(output_time / 1000000); -} - -time_ns :: () -> u64 { - output_time: Timestamp; - clock_time_get(.Realtime, 1, ^output_time); - return ~~output_time; -} - -sleep :: #match { - (seconds: u64) { sleep(nanoseconds=seconds * 1000000000); }, - (milliseconds: u64) { sleep(nanoseconds=milliseconds * 1000000); }, - - (nanoseconds: u64) { - tagged: SubscriptionTagged; - tagged.tag = .Clock; - tagged.clock = .{ - id = .Realtime, - timeout = cast(u64) nanoseconds, - precision = 1, - flags = ~~0, - }; - - subscription := Subscription.{ - userdata = 0, - u = tagged, - }; - - event: Event; - number_of_events: u32; - - error_code := poll_oneoff(^subscription, ^event, 1, ^number_of_events); - } -} diff --git a/core/wasi/env.onyx b/core/wasi/env.onyx deleted file mode 100644 index 7cd5b3bc..00000000 --- a/core/wasi/env.onyx +++ /dev/null @@ -1,57 +0,0 @@ -package core.env - -#local runtime :: package runtime -#if runtime.runtime != .Wasi - && runtime.runtime != .Onyx { - #error "'core.env' is only available with the 'wasi' and 'onyx' runtimes."; -} - - -use package wasi { environ_get, environ_sizes_get, Size } -#package map :: package core.map -#package memory :: package core.memory -#package string :: package core.string - -Environment :: struct { - vars : Map(str, str); - - buffer : [] u8; - buffer_allocator : Allocator; -} - -get_env :: (allocator := context.allocator) -> Environment { - env_count, env_buf_size : Size; - environ_sizes_get(^env_count, ^env_buf_size); - - env_var := memory.make_slice(cstr, env_count, allocator=allocator); - env_buf := memory.make_slice(u8, env_buf_size, allocator=allocator); - - environ_get(env_var.data, env_buf.data); - - while i := cast(i32) (env_var.count - 1); i >= 0 { - defer i -= 1; - - env_var[i] = cast(cstr) (cast(^u32) env_var.data)[i]; - } - - env_map := map.make(str, str, ""); - for env: env_var { - s := string.from_cstr(env); - var := string.read_until(^s, #char "="); - map.put(^env_map, var, string.advance(s, 1)); - } - - raw_free(allocator, env_var.data); - - return .{ - vars = env_map, - buffer = env_buf, - buffer_allocator = allocator, - }; -} - -free_env :: (use env: ^Environment) { - map.free(^vars); - - raw_free(buffer_allocator, buffer.data); -} diff --git a/core/wasi/wasi.onyx b/core/wasi/wasi.onyx deleted file mode 100644 index 73505639..00000000 --- a/core/wasi/wasi.onyx +++ /dev/null @@ -1,435 +0,0 @@ -package wasi - -Size :: #type u32; -Filesize :: #type u64; -Timestamp :: #type u64; - -ClockID :: enum (u32) { - Realtime :: 0x00; - Monotonic :: 0x01; - ProcessCPUTimeID :: 0x02; - ThreadCPUTimeID :: 0x03; -} - -Errno :: enum (u16) { - Success :: 0x00; - TooBig :: 0x01; - Access :: 0x02; - AddrInUse :: 0x03; - AddrNotAvail :: 0x04; - AFNoSupport :: 0x05; - Again :: 0x06; - Already :: 0x07; - BadFile :: 0x08; - BadMsg :: 0x09; - Busy :: 0x0a; - Canceled :: 0x0b; - Child :: 0x0c; - ConnAborted :: 0x0d; - ConnRefused :: 0x0e; - ConnReset :: 0x0f; - DeadLock :: 0x10; - DestAddrReq :: 0x11; - Domain :: 0x12; - DQUOT :: 0x13; - Exist :: 0x14; - Fault :: 0x15; - FileTooBig :: 0x16; - HostUnreach :: 0x17; - IdentRemoved :: 0x18; - IllegalSeq :: 0x19; - InProgress :: 0x1a; - Interrupt :: 0x1b; - Invalid :: 0x1c; - IO :: 0x1d; - IsConnection :: 0x1e; - IsDirectory :: 0x1f; - Loop :: 0x20; - MFile :: 0x21; - MLink :: 0x22; - MsgSize :: 0x23; - MultiHop :: 0x24; - NameTooLong :: 0x25; - NetDown :: 0x26; - NetReset :: 0x27; - NetUnreach :: 0x28; - NFile :: 0x29; - NoBufs :: 0x2a; - NoDev :: 0x2b; - NoEntry :: 0x2c; - NoExec :: 0x2d; - NoLock :: 0x2e; - NoLink :: 0x2f; - NoMemory :: 0x30; - NoMsg :: 0x31; - NoProtoOpt :: 0x32; - NoSpace :: 0x33; - NoSys :: 0x34; - NotConn :: 0x35; - NotDir :: 0x36; - NotEmpty :: 0x37; - NotRecover :: 0x38; - NotSock :: 0x39; - NotSupported :: 0x3a; - NoTTY :: 0x3b; - NXIO :: 0x3c; - Overflow :: 0x3d; - OwnerDead :: 0x3e; - Permission :: 0x3f; - Pipe :: 0x40; - Protocol :: 0x41; - ProtoNoSup :: 0x42; - Prototype :: 0x43; - Range :: 0x44; - ReadonlyFS :: 0x45; - SeekPipe :: 0x46; - Search :: 0x47; - Stale :: 0x48; - Timedout :: 0x49; - TextBusy :: 0x4a; - XDev :: 0x4b; - - NotCapable :: 0x4c; -} - -Rights :: enum #flags (u64) { - DataSync; - Read; - Seek; - FdStatSetFlags; - Sync; - Tell; - Write; - Advise; - Allocate; - PathCreateDirectory; - PathCreateFile; - PathLinkSource; - PathLinkTarget; - PathOpen; - ReadDir; - PathReadlink; - PathRenameSource; - PathRenameTarget; - PathFilestatGet; - PathFilestateSetSize; - PathFilestateSetTimes; - FilestatGet; - FilestatSetSize; - FilestatSetTimes; - PathSymlink; - PathRemoveDirectory; - PathUnlinkFile; - PollFDReadWrite; - SockShutdown; -} - -FileDescriptor :: #type i32; - -IOVec :: struct { - buf : u32; // actually a ^u8, but WASM is 32-bit at the moment; - len : u32; -} - -CIOVec :: #type IOVec; // Constant IOVec - -FileDelta :: #type i64; - -Whence :: enum (u8) { - Set :: 0x00; - Cur :: 0x01; - End :: 0x02; -} - -DirCookie :: #type u64; -DirNameLen :: #type u32; -INode :: #type u64; - -Filetype :: enum (u8) { - Unknown :: 0x00; - BlockDevice :: 0x01; - CharDevice :: 0x02; - Directory :: 0x03; - RegularFile :: 0x04; - SocketDgram :: 0x05; - SocketStream :: 0x06; - SymLink :: 0x07; -} - -DirEnt :: struct { - d_next : DirCookie; - d_ino : INode; - d_namlen : DirNameLen; - d_type : Filetype; -} - -Advice :: enum (u8) { - Normal :: 0x00; - Sequential :: 0x01; - Random :: 0x02; - WillNeed :: 0x03; - DontNeed :: 0x04; - NoReuse :: 0x05; -} - -FDFlags :: enum #flags (u16) { - Append; - DSync; - NonBlock; - RSync; - Sync; -} - -FDStat :: struct { - fs_filetype : Filetype; - fs_flags : FDFlags; - fs_rights_base : Rights; - fs_rights_inheriting : Rights; -} - -Device :: #type u64; - -FSTFlags :: enum #flags (u16) { - ATIM; - ATIM_NOW; - MTIM; - MTIM_NOW; -} - -LookupFlags :: enum #flags (u32) { - SymLinkFollow; -} - -OFlags :: enum #flags (u16) { - Creat; - Directory; - FailIfExists; - Trunc; -} - -LinkCount :: #type u64; - -FileStat :: struct { - dev : Device; - ino : INode; - filetype : Filetype; - - // ANGER(Brendan Hansen): I give the worst documentation in - // the entire world award to WASI, whose best documentation - // is a C header file, that is itself incorrect. nlink does - // not exist in the current version of wasmtime, and should - // not be here, but maybe in the future? Ugh. - // - // STILL ANGERY(Brendan Hansen): Actually, nlink does exist - // in the current version of wasmtime; however its actually - // a 32-bit value, not 64-bit, which means the offsets were - // incorrect. I fixed it for now, but when wasmtime updates - // the size of 'LinkCount' needs to be changed. - // - // EVEN ANGRIER(Brendan Hansen): ACTUALLY, I realized today - // that it has changed (at least for Wasmer). There was not - // any mention of this on any release documents, but hey, I - // guess this can actually be 64-bits now. - // - brendanfh 2020/12/05 - // - brendanfh 2020/12/07 - // - brendanfh 2021/07/10 - nlink : LinkCount; - - size : Filesize; - atim : Timestamp; - mtim : Timestamp; - ctim : Timestamp; -} - -Userdata :: #type u64; - -EventType :: enum (u8) { - Clock; - FDRead; - FDWrite; -} - -EventRWFlags :: enum #flags (u16) { - ReadWriteHangUp; -} - -EventFDReadWrite :: struct { - nbytes : Filesize; - flags : EventRWFlags; -} - -Event :: struct { - userdata : Userdata; - error : Errno; - type : EventType; - fd_readwrite : EventFDReadWrite; -} - -SubClockFlags :: enum #flags (u16) { - ClockAbsTime; -} - -SubscriptionClock :: struct { - id : ClockID; - timeout : Timestamp; - precision : Timestamp; - flags : SubClockFlags; -} - -SubscriptionFDReadWrite :: struct { - file_descriptor : FileDescriptor; -} - -SubscriptionTagged :: struct { - tag : EventType; - - use u : struct #union { - clock : SubscriptionClock; - fd_read : SubscriptionFDReadWrite; - fd_write : SubscriptionFDReadWrite; - }; -} - -Subscription :: struct { - userdata : Userdata; - - u : SubscriptionTagged; -} - -ExitCode :: #type u32; -Signal :: enum (u8) { - None; - Hup; - Int; - Quit; - Ill; - Trap; - Abrt; - Bus; - Fpe; - Kill; - USR1; - Segv; - USR2; - Pipe; - Alrm; - Term; - Chld; - Stop; - Tstp; - Ttin; - Urg; - Xcpu; - Xfsz; - Vtalrm; - Prof; - Winch; - Poll; - Pwr; - Sys; -} - -RIFlags :: enum #flags (u16) { - RecvPeek; - RecvWaitAll; -} - -ROFlags :: enum #flags (u16) { - RecvDataTruncated :: 1; -} - -SIFlags :: enum #flags (u16) { - None; -} - -SDFlags :: enum #flags (u16) { - RD; - WR; -} - -PreopenType :: enum (u8) { - Dir :: 0x00; -} - -PrestatDir :: struct { - pr_name_len : Size; -} - -PrestatTagged :: struct { - tag : PreopenType; - - u : struct #union { - dir : PrestatDir; - }; -} - - -// FUNCTIONS -args_get :: (argv: ^^u8, argv_buf: ^u8) -> Errno #foreign "wasi_snapshot_preview1" "args_get"--- -args_sizes_get :: (argc: ^Size, argv_buf_size: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "args_sizes_get" --- - -environ_get :: (environ: ^^u8, environ_buf: ^u8) -> Errno #foreign "wasi_snapshot_preview1" "environ_get" --- -environ_sizes_get :: (environc: ^Size, environ_buf_size: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "environ_sizes_get" --- - -clock_res_get :: (id: ClockID, resolution: ^Timestamp) -> Errno #foreign "wasi_snapshot_preview1" "clock_res_get" --- -clock_time_get :: (id: ClockID, precision: Timestamp, time: ^Timestamp) -> Errno #foreign "wasi_snapshot_preview1" "clock_time_get" --- - -fd_advise :: (fd: FileDescriptor, offset: Filesize, len: Filesize, advice: Advice) -> Errno #foreign "wasi_snapshot_preview1" "fd_advise" --- -fd_allocate :: (fd: FileDescriptor, offset: Filesize, len: Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_allocate" --- -fd_close :: (fd: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_close" --- -fd_datasync :: (fd: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_datasync" --- -fd_fdstat_get :: (fd: FileDescriptor, stat: ^FDStat) -> Errno #foreign "wasi_snapshot_preview1" "fd_fdstat_get" --- -fd_fdstat_set_flags :: (fd: FileDescriptor, flags: FDFlags) -> Errno #foreign "wasi_snapshot_preview1" "fd_fdstat_set_flags" --- -fd_fdstat_set_rights :: (fd: FileDescriptor, rights_base: Rights, rights_inheriting: Rights) -> Errno #foreign "wasi_snapshot_preview1" "fd_fdstat_set_rights" --- -fd_filestat_get :: (fd: FileDescriptor, buf: ^FileStat) -> Errno #foreign "wasi_snapshot_preview1" "fd_filestat_get" --- -fd_filestat_set_size :: (fd: FileDescriptor, size: Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_filestat_set_size" --- -fd_filestat_set_times :: (fd: FileDescriptor, atim: Timestamp, mtim: Timestamp, fst_flags: FSTFlags) -> Errno #foreign "wasi_snapshot_preview1" "fd_filestat_set_times" --- -fd_pread :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, offset: Filesize, nread: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_pread" --- -fd_prestat_get :: (fd: FileDescriptor, buf: ^PrestatTagged) -> Errno #foreign "wasi_snapshot_preview1" "fd_prestat_get" --- -fd_prestat_dir_name :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "fd_prestat_dir_name" --- -fd_pwrite :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, offset: Filesize, nwritten: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_pwrite" --- -fd_read :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, nread: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_read" --- -fd_readdir :: (fd: FileDescriptor, buf: ^u8, buf_len: Size, cookie: DirCookie, bufused: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_readdir" --- -fd_renumber :: (fd: FileDescriptor, to: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_renumber" --- -fd_seek :: (fd: FileDescriptor, offset: FileDelta, whence: Whence, newoffset: ^Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_seek" --- -fd_sync :: (fd: FileDescriptor) -> Errno #foreign "wasi_snapshot_preview1" "fd_sync" --- -fd_tell :: (fd: FileDescriptor, offset: ^Filesize) -> Errno #foreign "wasi_snapshot_preview1" "fd_tell" --- -fd_write :: (fd: FileDescriptor, iovs: ^IOVec, iovs_len: Size, nwritten: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "fd_write" --- - -path_create_directory :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_create_directory" --- -path_filestat_get :: (fd: FileDescriptor, flags: LookupFlags, path: str, buf: ^FileStat) -> Errno #foreign "wasi_snapshot_preview1" "path_filestat_get" --- -path_filestat_set_times :: (fd: FileDescriptor, flags: LookupFlags, path: str, atim: Timestamp, mtim: Timestamp, fst_flags: FSTFlags) -> Errno #foreign "wasi_snapshot_preview1" "path_filestat_set_times" --- - -path_link :: (fd: FileDescriptor, old_flags: LookupFlags, old_path: str, new_fd: FileDescriptor, new_path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_link" --- -path_open :: (fd: FileDescriptor - , dirflags: LookupFlags - , path: str - , oflags: OFlags - , fs_rights_base: Rights - , fs_rights_inherting: Rights - , fdflags: FDFlags - , opened_fd: ^FileDescriptor - ) -> Errno - #foreign "wasi_snapshot_preview1" "path_open" --- - -path_readlink :: (fd: FileDescriptor, path: str, buf: ^u8, buf_len: Size, bufused: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "path_readlink" --- -path_remove_directory :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_remove_directory" --- -path_rename :: (fd: FileDescriptor, old_path: str, new_fd: FileDescriptor, new_path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_rename" --- -path_symlink :: (old_path: ^u8, old_path_len: Size, fd: FileDescriptor, new_path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_symlink" --- -path_unlink_file :: (fd: FileDescriptor, path: str) -> Errno #foreign "wasi_snapshot_preview1" "path_unlink_file" --- - -poll_oneoff :: (in: ^Subscription, out: ^Event, nsubscriptions: Size, nevents: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "poll_oneoff" --- - -proc_exit :: (rval: ExitCode) -> void #foreign "wasi_snapshot_preview1" "proc_exit" --- -proc_raise :: (sig: Signal) -> Errno #foreign "wasi_snapshot_preview1" "proc_raise" --- - -sched_yield :: () -> Errno #foreign "wasi_snapshot_preview1" "sched_yield" --- - -random_get :: (buf: ^u8, buf_len: Size) -> Errno #foreign "wasi_snapshot_preview1" "random_get" --- - -sock_recv :: (fd: FileDescriptor, ri_data: ^IOVec, ri_data_len: Size, ri_flags: RIFlags, ro_datalen: ^Size, ro_flags: ^ROFlags) -> Errno #foreign "wasi_snapshot_preview1" "sock_recv" --- -sock_send :: (fd: FileDescriptor, si_data: ^IOVec, ri_data_len: Size, si_flags: SIFlags, so_datalen: ^Size) -> Errno #foreign "wasi_snapshot_preview1" "sock_send" --- -sock_shutdown :: (fd: FileDescriptor, how: SDFlags) -> Errno #foreign "wasi_snapshot_preview1" "sock_shutdown" --- - - - diff --git a/core/wasi/wasi_fs.onyx b/core/wasi/wasi_fs.onyx deleted file mode 100644 index 7bc6e7e6..00000000 --- a/core/wasi/wasi_fs.onyx +++ /dev/null @@ -1,336 +0,0 @@ -package runtime.fs - -#local runtime :: package runtime -#if runtime.runtime != .Wasi { - #error "The file system library is currently only available on the WASI runtime, and should only be included if that is the chosen runtime." -} - -use package core - -#local wasi :: package wasi -use package wasi { - FileDescriptor, - FDFlags, OFlags, Rights, - LookupFlags, Errno, - IOVec, Size, - FileStat, Whence -} - -FileData :: struct { - fd : FileDescriptor = -1; - - mode : os.OpenMode = .Invalid; - rights : Rights = ~~ 0; - flags : FDFlags = ~~ 0; -} - -__file_open :: (path: str, mode := os.OpenMode.Read) -> (FileData, os.FileError) { - // Requesting all of the rights because why not. - rights := - Rights.DataSync - | Rights.Read - | Rights.Seek - | Rights.FdStatSetFlags - | Rights.Sync - | Rights.Tell - | Rights.Write - | Rights.Advise - | Rights.Allocate - | Rights.PathCreateDirectory - | Rights.PathCreateFile - | Rights.PathLinkSource - | Rights.PathLinkTarget - | Rights.PathOpen - | Rights.ReadDir - | Rights.PathReadlink - | Rights.PathRenameSource - | Rights.PathRenameTarget - | Rights.PathFilestatGet - | Rights.PathFilestateSetSize - | Rights.PathFilestateSetTimes - | Rights.FilestatGet - | Rights.FilestatSetSize - | Rights.FilestatSetTimes - | Rights.PathSymlink - | Rights.PathRemoveDirectory - | Rights.PathUnlinkFile - | Rights.PollFDReadWrite; - - open_flags := cast(OFlags) 0; - fd_flags := FDFlags.Sync; - - switch mode { - case .Write { - open_flags |= OFlags.Creat | OFlags.Trunc; - rights |= Rights.Write | Rights.Seek | Rights.Tell; - } - - case .Append { - open_flags |= OFlags.Creat; - rights |= Rights.Write | Rights.Seek | Rights.Tell; - fd_flags |= FDFlags.Append; - } - - case .Read { - rights |= Rights.Read | Rights.Seek | Rights.Tell; - } - } - - file := FileData.{ fd = -1 }; - file.mode = mode; - file.rights = rights; - file.flags = fd_flags; - - // Currently the directory's file descriptor appears to always be 3 - // However, this is not necessarily correct, so also try a preopened directory - for DIR_FD: .[ 3, 4 ] { - if err := wasi.path_open( - DIR_FD, - .SymLinkFollow, - path, - open_flags, - rights, - rights, - fd_flags, - ^file.fd); - err == .Success { - return file, .None; - } - } - - // @TODO // provide a better error code. - return file, .NotFound; -} - -__file_close :: (file: FileData) -> os.FileError { - if wasi.fd_close(file.fd) != .Success { - return .BadFile; - } - - return .None; -} - -__file_stat :: (path: str, out: ^os.FileStat) -> bool { - fs: wasi.FileStat; - - exists := false; - for .[3, 4] { // Trying both preopened directories - err := wasi.path_filestat_get(it, .SymLinkFollow, path, ^fs); - if err == .Success { - exists = true; - out.size = ~~ fs.size; - - switch fs.filetype { - case .RegularFile do out.type = .RegularFile; - case .Directory do out.type = .Directory; - case .SymLink do out.type = .SymLink; - case #default do out.type = .Unknown; - } - } - } - - return exists; -} - -__file_exists :: (path: str) -> bool { - fs: wasi.FileStat; - - exists := false; - for .[3, 4] { // Trying both preopened directories - err := wasi.path_filestat_get(it, .SymLinkFollow, path, ^fs); - if err == .Success do exists = true; - } - - return exists; -} - -__file_remove :: (path: str) -> bool { - removed := false; - for .[3, 4] { // Trying both preopened directories - err := wasi.path_unlink_file(it, path); - if err == .Success do removed = true; - } - - return removed; -} - -__file_rename :: (old_path: str, new_path: str) -> bool { - renamed := false; - for .[3, 4] { // Trying both preopened directories - err := wasi.path_rename(it, old_path, it, new_path); - if err == .Success do renamed = true; - } - - return renamed; -} - -__file_stream_vtable := io.Stream_Vtable.{ - seek = (use fs: ^os.File, to: i32, whence: io.SeekFrom) -> io.Error { - // Currently, the new offset is just ignored. - newoffset : wasi.Filesize; - error := wasi.fd_seek(data.fd, ~~ to, ~~ whence, ^newoffset); - if error != .Success do return .BadFile; - - return .None; - }, - - tell = (use fs: ^os.File) -> (io.Error, u32) { - location : wasi.Filesize; - error := wasi.fd_tell(data.fd, ^location); - if error != .Success do return .BadFile, 0; - - return .None, ~~location; - }, - - read = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { - bytes_read : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_read(data.fd, ^vec, 1, ^bytes_read); - if error != .Success do return .BadFile, 0; - - return .None, bytes_read; - }, - - read_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { - bytes_read : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_pread(data.fd, ^vec, 1, ~~at, ^bytes_read); - - // FIX: Maybe report io.Error.OutOfBounds if the 'at' was out of bounds? - if error != .Success do return .BadFile, 0; - - return .None, bytes_read; - }, - - read_byte = (use fs: ^os.File) -> (io.Error, u8) { - bytes_read : wasi.Size; - byte : u8; - vec := IOVec.{ buf = cast(u32) ^byte, len = 1}; - error := wasi.fd_read(data.fd, ^vec, 1, ^bytes_read); - if error != .Success do return .BadFile, 0; - - return .None, byte; - }, - - write = (use fs: ^os.File, buffer: [] u8) -> (io.Error, u32) { - bytes_written : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_write(data.fd, ^vec, 1, ^bytes_written); - if error != .Success do return .BadFile, 0; - - return .None, bytes_written; - }, - - write_at = (use fs: ^os.File, at: u32, buffer: [] u8) -> (io.Error, u32) { - bytes_written : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_pwrite(data.fd, ^vec, 1, ~~at, ^bytes_written); - - // FIX: Maybe report io.Error.OutOfBounds if the 'at' was out of bounds? - if error != .Success do return .BadFile, 0; - - return .None, bytes_written; - }, - - write_byte = (use fs: ^os.File, byte: u8) -> io.Error { - bytes_written : wasi.Size; - byte_to_write := byte; - vec := IOVec.{ buf = cast(u32) ^byte_to_write, len = 1 }; - error := wasi.fd_write(data.fd, ^vec, 1, ^bytes_written); - if error != .Success do return .BadFile; - - return .None; - }, - - close = (use fs: ^os.File) -> io.Error { - __file_close(data); - return .None; - }, - - flush = (use fs: ^os.File) -> io.Error { - wasi.fd_datasync(data.fd); - return .None; - }, - - size = (use fs: ^os.File) -> i32 { - file_stat: FileStat; - if wasi.fd_filestat_get(data.fd, ^file_stat) != .Success do return 0; - - return ~~ file_stat.size; - }, -} - - -WasiDirectory :: struct { - dir_fd: FileDescriptor; - last_cookie: wasi.DirCookie; -} - -DirectoryData :: ^WasiDirectory; - -__dir_open :: (path: str, dir: ^DirectoryData) -> bool { - dir_fd: FileDescriptor; - err := wasi.path_open(4, .SymLinkFollow, path, .Directory, ~~0xffffffff, ~~0xffffffff, .Sync, ^dir_fd); - if err != .Success { - return false; - } - - d := new(WasiDirectory); - d.dir_fd = dir_fd; - d.last_cookie = 0; - - *dir = d; - return true; -} - -__dir_close :: (dir: DirectoryData) { - wasi.fd_close(dir.dir_fd); - cfree(dir); -} - -__dir_read :: (dir: DirectoryData, out_entry: ^os.DirectoryEntry) -> bool { - buffer: [512] u8; - bufused: u32; - - err := wasi.fd_readdir(dir.dir_fd, ~~buffer, 512, dir.last_cookie, ^bufused); - if err != .Success || bufused == 0 do return false; - - dirent := cast(^wasi.DirEnt) buffer; - switch dirent.d_type { - case .Unknown do out_entry.type = .Unknown; - case .BlockDevice do out_entry.type = .Block; - case .CharDevice do out_entry.type = .Char; - case .Directory do out_entry.type = .Directory; - case .RegularFile do out_entry.type = .RegularFile; - case .SymLink do out_entry.type = .SymLink; - case #default do out_entry.type = .Other; - } - - out_entry.identifier = ~~dirent.d_ino; - out_entry.name_length = dirent.d_namlen; - memory.set(~~^out_entry.name_data, 0, 256); - memory.copy(~~^out_entry.name_data, ~~(dirent + 1), math.min(dirent.d_namlen, sizeof typeof out_entry.name_data)); - - dir.last_cookie = dirent.d_next; - return true; -} - -__dir_create :: (path: str) -> bool { - created := false; - for .[3, 4] { // Trying both preopened directories - err := wasi.path_create_directory(it, path); - if err == .Success do created = true; - } - - return created; -} - -__dir_remove :: (path: str) -> bool { - removed := false; - for .[3, 4] { // Trying both preopened directories - err := wasi.path_remove_directory(it, path); - if err == .Success do removed = true; - } - - return removed; -} diff --git a/docs/ideas/platform_layer.md b/docs/ideas/platform_layer.md new file mode 100644 index 00000000..e14cee2c --- /dev/null +++ b/docs/ideas/platform_layer.md @@ -0,0 +1,132 @@ +Onyx Standard Library Platform Layer +==================================== + +Onyx has *soft* definition for what is expected from the supporting +platform within its standard library. This should be solidified into +a concrete set of types and functions that are expected to be implemented, +or _explicity not_ implemented for a given platform. + +This is where not having header files and having a fluid definition of +functions does hurt a little bit, because it is not possible to say +"This set of files should define all of these functions". Nonetheless, +this document will serve as that "header file" + +## Always Expected +- `__start() -> void` + + +## Files + +### Types +- `FileData` + +### Procedures +- `__file_open(path: str, mode: core.os.OpenMode) -> (FileData, core.os.FileError)` +- `__file_close(fd: FileData) -> core.os.FileError` +- `__file_stat(path: str, stat: ^os.FileStat) -> bool` +- `__file_exists(path: str) -> bool` +- `__file_remove(path: str) -> bool` +- `__file_rename(old_path, new_path: str) -> bool` + +### Values +- `__file_stream_vtable: io.Stream_Vtable` + + +## Directories + +### Types +- `DirectoryData` + +- `__dir_open(path: str, dir: ^DirectoryData) -> bool` +- `__dir_close(dir: DirectoryData) -> void` +- `__dir_read(dir: DirectoryData, out_entry: ^os.DirectoryEntry) -> bool` +- `__dir_create(path: str) -> bool` +- `__dir_remove(path: str) -> bool` + +### Values + + + +## Standard I/O + +### Types + +### Procedures +- `__output_string(s: str) -> u32` +- `__output_error(s: str) -> u32` +- `__read_from_input(buffer: [] u8) -> i32` + - `> 0` on success. Returns number of bytes + - `0` on no-input, but not error. + - `< 0` on error. + +### Values + + +## OS + +### Types + +### Procedures +- `__exit(code: i32) -> void` +- `__sleep(milliseconds: i32) -> void` + +### Values + + +## Time + +### Types + +### Procedures +- `__time() -> u64` +- `__time_localtime(time: u64, tm: ^core.time.Timestamp) -> void` +- `__time_gmtime(time: u64, tm: ^core.time.Timestamp) -> void` +- `__time_gmtime(tm: ^core.time.Timestamp) -> i64` +- `__time_strftime(buf: [] u8, format: cstr, tm: ^core.time.Timestamp) -> u32` + +### Values + + +## Threads + +### Types + +### Procedures +- `__spawn_thread(id: i32, tls_base, stack_base: rawptr, func: (rawptr) -> void, data: rawptr) -> bool` +- `__kill_thread(id: i32) -> i32` + +### Values + + +## Processes + +### Types +- `ProcessData` + +### Procedures +- `__process_spawn(path: str, args: [] str, non_blocking_io: bool, starting_directory: str) -> ProcessData` +- `__process_read(handle: ProcessData, buffer: [] u8) -> i32` +- `__process_write(handle: ProcessData, buffer: [] u8) -> i32` +- `__process_kill(handle: ProcessData) -> bool` +- `__process_wait(handle: ProcessData) -> os.ProcessResult` +- `__process_destroy(handle: ProcessData) -> void` + +### Values + + +## Platform Support Flags + +### Types + +### Procedures + +### Values + +- `Supports_Files :: bool` +- `Supports_Directories :: bool` +- `Supports_Os :: bool` +- `Supports_Processes :: bool` +- `Supports_Time :: bool` +- `Supports_Networking :: bool` +- `Supports_Type_Info :: bool` +- `Supports_Threads :: bool`