Browse Source

Merge pull request #3836 from laytan/run-wasi-demo-in-ci

wasi: make the demo run on wasi and run it in CI
gingerBill 1 year ago
parent
commit
5637ed9ecd

+ 7 - 1
.github/workflows/ci.yml

@@ -97,7 +97,7 @@ jobs:
       - name: Download LLVM (MacOS ARM)
         if: matrix.os == 'macos-14'
         run: |
-          brew install llvm@17
+          brew install llvm@17 wasmtime
           echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
 
       - name: Build Odin
@@ -147,6 +147,12 @@ jobs:
         run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
         if: matrix.os == 'ubuntu-latest'
 
+      - name: Run demo on WASI WASM32
+        run: |
+          ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -out:demo.wasm
+          wasmtime ./demo.wasm
+        if: matrix.os == 'macos-14'
+
   build_windows:
     name: Windows Build, Check, and Test
     runs-on: windows-2022

+ 5 - 0
base/runtime/entry_wasm.odin

@@ -22,6 +22,11 @@ when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
 		@(link_name="_start", linkage="strong", require, export)
 		_start :: proc "c" () {
 			context = default_context()
+
+			when ODIN_OS == .WASI {
+				_wasi_setup_args()
+			}
+
 			#force_no_inline _startup_runtime()
 			intrinsics.__entry_point()
 		}

+ 47 - 3
base/runtime/os_specific_wasi.odin

@@ -2,10 +2,54 @@
 //+private
 package runtime
 
-import "core:sys/wasm/wasi"
+foreign import wasi "wasi_snapshot_preview1"
+
+@(default_calling_convention="contextless")
+foreign wasi {
+	fd_write :: proc(
+		fd: i32,
+		iovs: [][]byte,
+		n: ^uint,
+	) -> u16 ---
+
+	@(private="file")
+	args_sizes_get :: proc(
+		num_of_args:  ^uint,
+		size_of_args: ^uint,
+	) -> u16 ---
+
+	@(private="file")
+	args_get :: proc(
+		argv:     [^]cstring,
+		argv_buf: [^]byte,
+	) -> u16 ---
+}
 
 _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
-	data_iovec := (wasi.ciovec_t)(data)
-	n, err := wasi.fd_write(1, {data_iovec})
+	n: uint
+	err := fd_write(1, {data}, &n)
 	return int(n), _OS_Errno(err)
 }
+
+_wasi_setup_args :: proc() {
+	num_of_args, size_of_args: uint
+	if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 {
+		return
+	}
+
+	err: Allocator_Error
+	if args__, err = make([]cstring, num_of_args); err != nil {
+		return
+	}
+
+	args_buf: []byte
+	if args_buf, err = make([]byte, size_of_args); err != nil {
+		delete(args__)
+		return
+	}
+
+	if errno := args_get(raw_data(args__), raw_data(args_buf)); errno != 0 {
+		delete(args__)
+		delete(args_buf)
+	}
+}

+ 1 - 1
base/runtime/wasm_allocator.odin

@@ -760,7 +760,7 @@ free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) {
 	defer unlock(a)
 
 	size := region.size
-	assert(region_is_in_use(region), "double free", loc=loc)
+	assert(region_is_in_use(region), "double free or corrupt region", loc=loc)
 
 	prev_region_size_field := ([^]uint)(region)[-1]
 	prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)

+ 0 - 1
core/encoding/cbor/tags.odin

@@ -95,7 +95,6 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string
 }
 
 // Controls initialization of default tag implementations.
-// JS and WASI default to a panic allocator so we don't want to do it on those.
 INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR)
 
 @(private, init, disabled=!INITIALIZE_DEFAULT_TAGS)

+ 12 - 0
core/os/os_wasi.odin

@@ -6,6 +6,8 @@ import "base:runtime"
 Handle :: distinct i32
 Errno :: distinct i32
 
+INVALID_HANDLE :: -1
+
 ERROR_NONE :: Errno(wasi.errno_t.SUCCESS)
 
 O_RDONLY   :: 0x00000
@@ -26,6 +28,16 @@ stdout: Handle = 1
 stderr: Handle = 2
 current_dir: Handle = 3
 
+args := _alloc_command_line_arguments()
+
+_alloc_command_line_arguments :: proc() -> (args: []string) {
+	args = make([]string, len(runtime.args__))
+	for &arg, i in args {
+		arg = string(runtime.args__[i])
+	}
+	return
+}
+
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	iovs := wasi.ciovec_t(data)
 	n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})

+ 5 - 1
core/thread/thread.odin

@@ -6,6 +6,8 @@ import "base:intrinsics"
 
 _ :: intrinsics
 
+IS_SUPPORTED :: _IS_SUPPORTED
+
 Thread_Proc :: #type proc(^Thread)
 
 MAX_USER_ARGUMENTS :: 8
@@ -58,7 +60,9 @@ Thread :: struct {
 	creation_allocator: mem.Allocator,
 }
 
-#assert(size_of(Thread{}.user_index) == size_of(uintptr))
+when IS_SUPPORTED {
+	#assert(size_of(Thread{}.user_index) == size_of(uintptr))
+}
 
 Thread_Priority :: enum {
 	Normal,

+ 0 - 47
core/thread/thread_js.odin

@@ -1,47 +0,0 @@
-//+build js
-package thread
-
-import "base:intrinsics"
-import "core:sync"
-import "core:mem"
-
-Thread_Os_Specific :: struct {}
-
-_thread_priority_map := [Thread_Priority]i32{
-	.Normal = 0,
-	.Low = -2,
-	.High = +2,
-}
-
-_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
-	unimplemented("core:thread procedure not supported on js target")
-}
-
-_start :: proc(t: ^Thread) {
-	unimplemented("core:thread procedure not supported on js target")
-}
-
-_is_done :: proc(t: ^Thread) -> bool {
-	unimplemented("core:thread procedure not supported on js target")
-}
-
-_join :: proc(t: ^Thread) {
-	unimplemented("core:thread procedure not supported on js target")
-}
-
-_join_multiple :: proc(threads: ..^Thread) {
-	unimplemented("core:thread procedure not supported on js target")
-}
-
-_destroy :: proc(thread: ^Thread) {
-	unimplemented("core:thread procedure not supported on js target")
-}
-
-_terminate :: proc(using thread : ^Thread, exit_code: int) {
-	unimplemented("core:thread procedure not supported on js target")
-}
-
-_yield :: proc() {
-	unimplemented("core:thread procedure not supported on js target")
-}
-

+ 47 - 0
core/thread/thread_other.odin

@@ -0,0 +1,47 @@
+//+build js, wasi, orca
+package thread
+
+import "base:intrinsics"
+
+_IS_SUPPORTED :: false
+
+Thread_Os_Specific :: struct {}
+
+_thread_priority_map := [Thread_Priority]i32{
+	.Normal = 0,
+	.Low = -2,
+	.High = +2,
+}
+
+_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
+	unimplemented("core:thread procedure not supported on target")
+}
+
+_start :: proc(t: ^Thread) {
+	unimplemented("core:thread procedure not supported on target")
+}
+
+_is_done :: proc(t: ^Thread) -> bool {
+	unimplemented("core:thread procedure not supported on target")
+}
+
+_join :: proc(t: ^Thread) {
+	unimplemented("core:thread procedure not supported on target")
+}
+
+_join_multiple :: proc(threads: ..^Thread) {
+	unimplemented("core:thread procedure not supported on target")
+}
+
+_destroy :: proc(thread: ^Thread) {
+	unimplemented("core:thread procedure not supported on target")
+}
+
+_terminate :: proc(using thread : ^Thread, exit_code: int) {
+	unimplemented("core:thread procedure not supported on target")
+}
+
+_yield :: proc() {
+	unimplemented("core:thread procedure not supported on target")
+}
+

+ 2 - 0
core/thread/thread_unix.odin

@@ -6,6 +6,8 @@ import "core:sync"
 import "core:sys/unix"
 import "core:time"
 
+_IS_SUPPORTED :: true
+
 CAS :: sync.atomic_compare_exchange_strong
 
 // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t.

+ 2 - 0
core/thread/thread_windows.odin

@@ -6,6 +6,8 @@ import "base:intrinsics"
 import "core:sync"
 import win32 "core:sys/windows"
 
+_IS_SUPPORTED :: true
+
 Thread_Os_Specific :: struct {
 	win32_thread:    win32.HANDLE,
 	win32_thread_id: win32.DWORD,

+ 1 - 0
examples/demo/demo.odin

@@ -1140,6 +1140,7 @@ prefix_table := [?]string{
 
 print_mutex := b64(false)
 
+@(disabled=!thread.IS_SUPPORTED)
 threading_example :: proc() {
 	fmt.println("\n# threading_example")