added: networking and more to wasi platform layer
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 18 Oct 2023 02:36:52 +0000 (21:36 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 18 Oct 2023 02:36:52 +0000 (21:36 -0500)
core/runtime/platform/wasi/platform.onyx
core/runtime/platform/wasi/wasi_fs.onyx
core/runtime/platform/wasi/wasix_defs.onyx [new file with mode: 0644]
core/runtime/platform/wasi/wasix_misc.onyx [new file with mode: 0644]
core/runtime/platform/wasi/wasix_net.onyx [new file with mode: 0644]

index 85dd6f866f7129f0a89adbae1d3ffa92a6722116..3b8ac9a95ef1826d27061738318f52b3539c7def 100644 (file)
@@ -32,13 +32,24 @@ Supports_Directories :: true
 Supports_Os :: true
 Supports_Processes :: false
 Supports_Time :: true
-Supports_Networking :: false
 Supports_Type_Info :: true
 Supports_Threads :: true
 Supports_Env_Vars :: true
-Supports_Futexes :: false
-Supports_TTY :: false
 
+#if #defined(runtime.vars.WASIX) {
+    Supports_Networking :: true
+    Supports_Futexes :: true
+
+    #load "./wasix_defs"
+    #load "./wasix_net"
+    #load "./wasix_misc"
+
+} else {
+    Supports_Networking :: false
+    Supports_Futexes :: false
+}
+
+Supports_TTY :: false
 
 __output_string :: (s: str) -> u32 {
     STDOUT_FILENO :: 1
@@ -64,15 +75,17 @@ __read_from_input :: (buffer: [] u8) -> i32 {
 }
 
 __wait_for_input :: (timeout: i32) -> bool {
+    STDIN_FILENO :: 0
+
     events := 1;
     subscriptions: [2] Subscription;
 
     fd_tagged: SubscriptionTagged;
     fd_tagged.tag = .FDRead;
-    fd_tagged.fd_read = .{ file_description = STDIN_FILENO, };
+    fd_tagged.fd_read = .{ file_descriptor = STDIN_FILENO };
     subscriptions[0] = .{
         userdata = 0,
-        u = tagged,
+        u = fd_tagged,
     };
 
     clock_tagged: SubscriptionTagged;
@@ -82,7 +95,7 @@ __wait_for_input :: (timeout: i32) -> bool {
         clock_tagged.clock = .{
             id = .Realtime,
             timeout = cast(u64) timeout * 1000000,
-            precision
+            precision = 1,
         };
 
         subscriptions[1] = .{
@@ -94,7 +107,7 @@ __wait_for_input :: (timeout: i32) -> bool {
     event: [2] Event;
     number_of_events: u32;
 
-    error_code := poll_oneoff(&subscription, &event, 2, &number_of_events);
+    error_code := poll_oneoff(~~subscriptions, ~~event, 2, &number_of_events);
 
     if number_of_events == 0 do return false;
     if event[0].type == .Clock do return false;
@@ -137,6 +150,61 @@ __random_get :: (buf: [] u8) {
     random_get(buf.data, buf.length);
 }
 
+PollDescription :: struct {
+    fd: wasi.FileDescriptor;
+    in: core.io.PollEvent;
+    out: core.io.PollEvent;
+}
+
+__poll :: (fds: [] PollDescription, timeout: i32) -> void {
+    count := fds.count;
+    if timeout == 0 {
+        timeout = 1;
+    }
+
+    if timeout > 0 {
+        count += 1; 
+    }
+
+    subs := core.alloc.array_from_stack(wasi.Subscription, count);
+    for i: fds.count {
+        subs[i].userdata = ~~ i;
+        subs[i].u.tag = switch fds[i].in {
+            case .None => .FDRead;
+            case .Read => .FDRead;
+            case .Write => .FDWrite;
+            case .Closed => .FDRead;
+        };
+        subs[i].u.u.fd_read = .{ file_descriptor = cast(i32) fds[i].fd };
+    }
+
+    if timeout > 0 {
+        subs[count - 1].userdata = 0;
+        subs[count - 1].u.tag = .Clock;
+        subs[count - 1].u.u.clock = .{
+            id = .Realtime,
+            timeout = cast(u64) timeout * 1000000,
+            precision = 1,
+        };
+    }
+
+    events := core.alloc.array_from_stack(wasi.Event, count);
+    number_of_events: u32;
+
+    error_code := wasi.poll_oneoff(~~subs.data, ~~events.data, count, &number_of_events);
+    if error_code != .Success do return;
+
+    if number_of_events == 0 do return;
+    for ev: events[0 .. number_of_events] {
+        if ev.type !=.Clock {
+            if ev.fd_readwrite.nbytes > 0 {
+                i := cast(i32) ev.userdata;
+                fds[i].out = fds[i].in;
+            }
+        }
+    }
+}
+
 
 // Sets up everything needed for execution.
 __start :: () {
index b2589dbd1a514d59044b339dec03c402d605e89f..61a5679e35d28cfa4300d68f28f2b8dd3f4ef2f4 100644 (file)
@@ -259,44 +259,15 @@ __file_stream_vtable := io.Stream_Vtable.{
         return ~~ file_stat.size;
     },
 
-    poll = (use fs: &os.File, ev: io.PollEvent, timeout: i32) -> (Error, bool) {
-        // TODO: Not entirely sure this is implemented correctly.
-        events := 1;
-        subscriptions: [2] Subscription;
-
-        fd_tagged: SubscriptionTagged;
-        fd_tagged.tag = .FDRead;
-        fd_tagged.fd_read = .{ file_description = cast(i64) data, };
-        subscriptions[0] = .{
-            userdata = 0,
-            u = tagged,
-        };
-
-        clock_tagged: SubscriptionTagged;
-        if timeout > 0 {
-            events += 1;
-            clock_tagged.tag = .Clock;
-            clock_tagged.clock = .{
-                id = .Realtime,
-                timeout = cast(u64) timeout * 1000000,
-                precision
-            };
-
-            subscriptions[1] = .{
-                userdata = 0,
-                u = clock_tagged,
-            };
-        }
-
-        event: [2] Event;
-        number_of_events: u32;
-
-        error_code := poll_oneoff(&subscription, &event, 2, &number_of_events);
+    poll = (use fs: &os.File, ev: io.PollEvent, timeout: i32) -> (io.Error, bool) {
+        p: [1] PollDescription = .[.{
+            fd = data.fd,
+            in = ev,
+        }];
 
-        if number_of_events == 0 do return .None, false;
-        if event[0].type == .Clock do return .None, false;
+        runtime.platform.__poll(p, timeout);
 
-        return .None, event[0].fd_readwrite.nbytes > 0;
+        return .None, p[0].out == ev;
     }
 }
 
diff --git a/core/runtime/platform/wasi/wasix_defs.onyx b/core/runtime/platform/wasi/wasix_defs.onyx
new file mode 100644 (file)
index 0000000..3a2d3cb
--- /dev/null
@@ -0,0 +1,179 @@
+package wasi
+
+#if !#defined(WASIX_VERSION) {
+    WASIX_VERSION :: "wasix_32v1"
+}
+
+EventFDFlags :: enum {
+}
+
+TTY :: struct {
+    cols: u32;
+    rows: u32;
+    width: u32;
+    height: u32;
+    stdin_tty: bool;
+    stdout_tty: bool;
+    stderr_tty: bool;
+    echo: bool;
+    line_buffered: bool;
+    line_feeds: bool;
+}
+
+OptionTimestamp :: union #tag_type u8 {
+    None: void;
+    Some: Timestamp;
+}
+
+SockStatus :: enum {
+    Opening;
+    Opened;
+    Closed;
+    Failed;
+}
+
+AddrIPv4 :: struct {
+    n0, n1, h0, h1: u8;
+}
+
+AddrIPv6 :: struct {
+    n0, n1, n2, n3, h0, h1, h2, h3: u8;
+    flow_info1: u16;
+    flow_info0: u16;
+    scope_id1: u16;
+    scope_id0: u16;
+}
+
+Addr :: union #tag_type u8 {
+    Unspec: struct {
+        n0: u8;
+    };
+
+    Ipv4: AddrIPv4;
+
+    Ipv6: AddrIPv6;
+
+    Unix: [108] u8;
+}
+
+AddrPort :: union #tag_type u8 {
+    Unspec: struct {
+        port: u16;
+        n0: u8;
+    };
+
+    Ipv4: struct {
+        port: u16;
+        addr: AddrIPv4;
+    };
+
+    Ipv6: struct {
+        port: u16;
+        addr: AddrIPv6;
+    };
+
+    Unix: struct {
+        path: [108] u8;
+    }
+}
+
+AddressFamily :: enum {
+    Unspec;
+    Inet;
+    Inet6;
+    Unix;    
+}
+
+SockType :: enum {
+    Unused;
+    Stream;
+    Dgram;
+    Raw;
+    Seqpacket;
+}
+
+SockProto :: enum {
+    IP;
+    ICMP;
+    IGMP;
+    IPIP :: 4;    
+    TCP :: 6;
+    EGP :: 8;
+    PUP :: 12;
+    UDP :: 17;
+    IDP :: 22;
+    DCCP :: 33;
+    IPV6 :: 41;
+    ICMPV6 :: 58;
+}
+
+SockOption :: enum {
+    Noop;
+    ReusePort;
+    ReuseAddr;
+    NoDelay;
+    DontRoute;
+    OnlyV6;
+    Broadcast;
+    MulticastLoopV4;
+    MulticastLoopV6;
+    Promiscuous;
+    Listening;
+    LastError;
+    KeepAlive;
+    Linger;
+    OOBInline;
+    RecvBufSize;
+    SendBufSize;
+    RecvLowat;
+    SendLowat;
+    RecvTimeout;
+    SendTimeout;
+    ConnectTimeout;
+    AcceptTimeout;
+    TTL;
+    MulticastTTLV5;
+    Type;
+    Proto;
+}
+
+#foreign WASIX_VERSION {
+    fd_dup :: (fd: FileDescriptor, out: &FileDescriptor) -> Errno ---
+    fd_event :: (init_val: u64, flags: EventFDFlags, fd: &FileDescriptor) -> Errno ---
+    fd_pipe :: (fd1, fd2: &FileDescriptor) -> Errno ---
+
+    tty_get :: (state: &TTY) -> Errno ---
+    tty_set :: (state: &TTY) -> Errno ---
+
+    getcwd :: (path: [&] u8, pathlen: &u32) -> Errno ---
+    chdir  :: (path: cstr) -> Errno ---
+
+    thread_spawn_v2    :: (args: rawptr, tid: &u32) -> Errno ---
+    thread_sleep       :: (duration: Timestamp) -> Errno ---
+    thread_id          :: (tid: &u32) -> Errno ---
+    thread_join        :: (tid: u32) -> Errno ---
+    thread_parallelism :: (size: &u32) -> Errno ---
+
+    futex_wait     :: (futex: &u32, expected: u32, timeout: &OptionTimestamp, out: &bool) -> Errno ---
+    futex_wake     :: (futex: &u32, out: &bool) -> Errno ---
+    futex_wake_all :: (futex: &u32, out: &bool) -> Errno ---
+
+    sock_status :: (fd: FileDescriptor, out: &SockStatus) -> Errno ---
+    sock_addr_local :: (fd: FileDescriptor, addr: &AddrPort) -> Errno ---
+    sock_addr_peer  :: (fd: FileDescriptor, addr: &AddrPort) -> Errno ---
+    sock_open :: (af: AddressFamily, socktype: SockType, sockproto: SockProto, out: &FileDescriptor) -> Errno ---
+    sock_set_opt_flag :: (fd: FileDescriptor, sockopt: SockOption, flag: bool) -> Errno ---
+    sock_get_opt_flag :: (fd: FileDescriptor, sockopt: SockOption, flag: &bool) -> Errno ---
+    sock_set_opt_time :: (fd: FileDescriptor, sockopt: SockOption, flag: &OptionTimestamp) -> Errno ---
+    sock_get_opt_time :: (fd: FileDescriptor, sockopt: SockOption, flag: &OptionTimestamp) -> Errno ---
+    sock_set_opt_size :: (fd: FileDescriptor, sockopt: SockOption, flag: u64) -> Errno ---
+    sock_get_opt_size :: (fd: FileDescriptor, sockopt: SockOption, flag: &u64) -> Errno ---
+    sock_bind :: (fd: FileDescriptor, addr: &AddrPort) -> Errno ---
+    sock_listen :: (fd: FileDescriptor, backlog: i32) -> Errno ---
+    sock_accept_v2 :: (fd: FileDescriptor, flags: FDFlags, out_sock: &FileDescriptor, out_addr: &AddrPort) -> Errno ---
+    sock_connect :: (fd: FileDescriptor, addr: &AddrPort) -> Errno ---
+    sock_recv_from :: (fd: FileDescriptor, data: &IOVec, data_len: Size, flags: RIFlags, written: &Size, oflags: &ROFlags, addr: &AddrPort) -> Errno ---
+    sock_send_to :: (fd: FileDescriptor, data: &IOVec, data_len: Size, flags: SIFlags, addr: &AddrPort, sent: &Size) -> Errno ---
+
+    resolve      :: (host: cstr, port: u16, addr: [&] Addr, addr_len: Size, retaddrs: &Size) -> Errno ---
+}
\ No newline at end of file
diff --git a/core/runtime/platform/wasi/wasix_misc.onyx b/core/runtime/platform/wasi/wasix_misc.onyx
new file mode 100644 (file)
index 0000000..e0d2d72
--- /dev/null
@@ -0,0 +1,27 @@
+package runtime.platform
+
+use wasi
+
+__futex_wait :: (addr: rawptr, expected: i32, timeout: i32) -> i32 {
+    tm: wasi.OptionTimestamp;
+    if timeout < 0 {
+        tm = .{ None = .{} };
+    } else {
+        tm = .{ Some = ~~timeout };
+    }
+
+    out: bool;
+    if wasi.futex_wait(addr, expected, &tm, &out) != .Success do return 0;
+    return 1 if out else 0;
+}
+
+__futex_wake :: (addr: rawptr, maximum: i32) -> i32 {
+    out: bool;
+    if (maximum == -1) {
+        if wasi.futex_wake_all(addr, &out) != .Success do return 0;
+    } else {
+        if wasi.futex_wake(addr, &out) != .Success do return 0;
+    }
+
+    return 1 if out else 0;
+}
\ No newline at end of file
diff --git a/core/runtime/platform/wasi/wasix_net.onyx b/core/runtime/platform/wasi/wasix_net.onyx
new file mode 100644 (file)
index 0000000..6abcc07
--- /dev/null
@@ -0,0 +1,221 @@
+package runtime.platform
+
+use wasi
+use core.net {
+    SocketFamily,
+    SocketProto,
+    SocketType,
+    SocketOption,
+    SocketAddress,
+    SocketShutdown,
+    SocketStatus
+}
+use core {Result, string, io}
+
+SocketData :: wasi.FileDescriptor
+
+__net_sock_create :: (af: SocketFamily, type: SocketType, proto: SocketProto) -> Result(SocketData, io.Error) {
+    family    := cast(wasi.AddressFamily) cast(u32) af;
+    socktype  := switch type {
+        case .Stream => wasi.SockType.Stream;
+        case .Dgram  => wasi.SockType.Dgram;
+    };
+    sockproto := cast(wasi.SockProto) cast(u32) proto;
+
+    sock: wasi.FileDescriptor;
+    err := wasi.sock_open(family, socktype, sockproto, &sock);
+
+    if err == .Success {
+        return .{ Ok = sock };
+    }
+
+    return .{ Err = .CreationFailed };
+}
+
+__net_sock_status :: (s: SocketData) -> SocketStatus {
+    status: wasi.SockStatus;
+    if wasi.sock_status(s, &status) != .Success {
+        return .Errored;
+    }
+
+    return switch status {
+        case .Opening => .Opening;
+        case .Opened  => .Open;
+        case .Closed  => .Closed;
+        case .Failed  => .Errored;
+    };
+}
+
+__net_sock_opt_flag :: (s: SocketData, sockopt: SocketOption, flag: bool) -> bool {
+    if sockopt == .NonBlocking {
+        return wasi.fd_fdstat_set_flags(s, wasi.FDFlags.NonBlock if flag else 0) == .Success;
+    }
+
+    opt := switch sockopt {
+        case .Broadcast => wasi.SockOption.Broadcast;
+        case .ReuseAddress => wasi.SockOption.ReuseAddr;
+        case #default => wasi.SockOption.Noop;
+    };
+    return wasi.sock_set_opt_flag(s, opt, flag) == .Success;
+}
+
+__net_sock_opt_time :: (s: SocketData, sockopt: SocketOption, time: ? u64) -> bool {
+    return false;
+}
+
+__net_sock_opt_size :: (s: SocketData, sockopt: SocketOption, size: i64) -> bool {
+    return false;
+}
+
+__net_sock_bind :: (s: SocketData, addr: &SocketAddress) -> bool {
+    target: wasi.AddrPort;
+    socket_addr_to_wasix_addr(addr, &target);
+
+    return wasi.sock_bind(s, &target) == .Success;
+}
+
+__net_sock_listen :: (s: SocketData, backlog: i32) -> bool {
+    return wasi.sock_listen(s, backlog) == .Success;
+}
+
+__net_sock_accept :: (s: SocketData, out: &SocketAddress) -> Result(SocketData, io.Error) {
+    new_sock: SocketData;
+    addr: wasi.AddrPort;
+    switch wasi.sock_accept_v2(s, 0, &new_sock, &addr) {
+        case .Success ---
+        case .Again do return .{ Err = .NoData };
+        case #default do return .{ Err = .OperationFailed };
+    }
+
+    wasi_addr_to_socket_address(&addr, out);
+
+    return .{ Ok = new_sock };
+}
+
+__net_sock_connect :: (s: SocketData, addr: &SocketAddress) -> io.Error {
+    target: wasi.AddrPort;
+    socket_addr_to_wasix_addr(addr, &target);
+
+    return switch wasi.sock_connect(s, &target) {
+        case .Success => .None;
+        case .ConnRefused,
+             .ConnAborted,
+             .ConnReset
+             => .ConnectFailed;
+
+        case #default => .OperationFailed;
+    };
+}
+
+__net_sock_recv_from :: (s: SocketData, buf: [] u8, out: &SocketAddress) -> Result(i32, io.Error) {
+    vec := wasi.IOVec.{ buf = cast(i32) buf.data, len = buf.count };
+
+    out_len: u32;
+    out_flags: wasi.ROFlags;
+    sender: wasi.AddrPort;
+    return switch wasi.sock_recv_from(s, &vec, 1, 0, &out_len, &out_flags, &sender) {
+        case .Success => do {
+            wasi_addr_to_socket_address(&sender, out);
+            return Result(i32, io.Error).{ Ok = out_len };
+        };
+        case .Again   => .{ Err = .NoData };
+        case #default => .{ Err = .EOF };
+    };
+}
+
+__net_sock_send_to :: (s: SocketData, buf: [] u8, addr: &SocketAddress) -> Result(i32, io.Error) {
+    vec := wasi.IOVec.{ buf = cast(i32) buf.data, len = buf.count };
+
+    out_len: u32;
+    target: wasi.AddrPort;
+    socket_addr_to_wasix_addr(addr, &target);
+    return switch wasi.sock_send_to(s, &vec, 1, 0, &target, &out_len) {
+        case .Success => Result(i32, io.Error).{ Ok = out_len };
+        case .Again   => Result(i32, io.Error).{ Err = .NoData };
+        case #default => Result(i32, io.Error).{ Err = .EOF };
+    };
+}
+
+__net_sock_recv :: (s: SocketData, buf: [] u8) -> Result(i32, io.Error) {
+    vec := wasi.IOVec.{ buf = cast(i32) buf.data, len = buf.count };
+
+    out_len: u32;
+    out_flags: wasi.ROFlags;
+    return switch wasi.sock_recv(s, &vec, 1, 0, &out_len, &out_flags) {
+        case .Success => Result(i32, io.Error).{ Ok = out_len };
+        case .Again   => Result(i32, io.Error).{ Err = .NoData };
+        case #default => Result(i32, io.Error).{ Err = .EOF };
+    };
+}
+
+__net_sock_send :: (s: SocketData, buf: [] u8) -> Result(i32, io.Error) {
+    vec := wasi.IOVec.{ buf = cast(i32) buf.data, len = buf.count };
+
+    out_len: u32;
+    return switch wasi.sock_send(s, &vec, 1, 0, &out_len) {
+        case .Success => Result(i32, io.Error).{ Ok = out_len };
+        case .Again   => Result(i32, io.Error).{ Err = .NoData };
+        case #default => Result(i32, io.Error).{ Err = .EOF };
+    };
+}
+
+__net_sock_shutdown :: (s: SocketData, how: SocketShutdown) -> io.Error {
+    sd := switch how {
+        case .Read => wasi.SDFlags.RD;
+        case .Write => wasi.SDFlags.WR;
+        case .ReadWrite => wasi.SDFlags.RD | wasi.SDFlags.WR;
+    };
+
+    return switch wasi.sock_shutdown(s, sd) {
+        case .Success => .None;
+        case #default => .OperationFailed;
+    };
+}
+
+__net_sock_close :: (s: SocketData) -> void {
+    wasi.fd_close(s);
+}
+
+__net_resolve :: (host: str, port: u16, out_addrs: [] SocketAddress) -> i32 {
+}
+
+
+#package {
+    socket_addr_to_wasix_addr :: (in: &SocketAddress, out: &wasi.AddrPort) {
+        switch *in {
+            case &path: .Unix {
+                *out = .{ Unix = .{ *cast(&[108] u8) path } };
+            }
+
+            case &inet: .Inet {
+                *out = .{ Ipv4 = .{ inet.port, *cast(&wasi.AddrIPv4)&inet.addr } };
+            }
+
+            case &inet: .Inet6 {
+                // Not entirely right...
+                *out = .{ Ipv6 = .{ inet.port, *cast(&wasi.AddrIPv6)&inet.addr } };
+            }
+
+            case .HostPort ---
+        }
+    }
+
+    wasi_addr_to_socket_address :: (in: &wasi.AddrPort, out: &SocketAddress) {
+        switch *in {
+            case .Unspec ---
+
+            case &inet: .Ipv4 {
+                *out = .{ Inet = .{ inet.port, *cast(&u32)&inet.addr } };
+            }
+
+            case &inet: .Ipv6 {
+                *out = .{ Inet6 = .{ inet.port, *cast(&[16] u8)&inet.addr } };
+            }
+
+            case &unix: .Unix {
+                p: [256] u8 = *cast(&[256] u8) &unix.path;
+                *out = .{ Unix = p };
+            }
+        }
+    }
+}
\ No newline at end of file