diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 85859e8c3..d71522936 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -190,6 +190,15 @@ constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- +// `timeout_ns` is maximum number of nanoseconds the calling thread will be blocked for +// A negative value will be blocked forever +// Return value: +// 0 - indicates that the thread blocked and then was woken up +// 1 - the loaded value from `ptr` did not match `expected`, the thread did not block +// 2 - the thread blocked, but the timeout +wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 --- +wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) --- + // Darwin targets only objc_object :: struct{} diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin new file mode 100644 index 000000000..9e96614d6 --- /dev/null +++ b/core/sync/futex_wasm.odin @@ -0,0 +1,36 @@ +//+private +//+build wasm32 +package sync + +import "core:intrinsics" +import "core:time" + +_futex_wait :: proc(f: ^Futex, expected: u32) -> bool { + s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) + return s != 0 +} + +_futex_wait_with_timeout :: proc(f: ^Futex, expected: u32, duration: time.Duration) -> bool { + s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration)) + return s != 0 + +} + +_futex_signal :: proc(f: ^Futex) { + loop: for { + s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) + if s >= 1 { + return + } + } +} + +_futex_broadcast :: proc(f: ^Futex) { + loop: for { + s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0)) + if s >= 0 { + return + } + } +} + diff --git a/core/sync/primitives_wasm.odin b/core/sync/primitives_wasm.odin new file mode 100644 index 000000000..283971ac5 --- /dev/null +++ b/core/sync/primitives_wasm.odin @@ -0,0 +1,8 @@ +//+private +//+build wasm32 +package sync + +_current_thread_id :: proc "contextless" () -> int { + // TODO(bill): _current_thread_id for wasm32 + return 0 +} \ No newline at end of file diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 9a5d1c554..65983423b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4473,6 +4473,99 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } break; + case BuiltinProc_wasm_memory_atomic_wait32: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand ptr = {}; + Operand expected = {}; + Operand timeout = {}; + check_expr(c, &ptr, ce->args[0]); if (ptr.mode == Addressing_Invalid) return false; + check_expr(c, &expected, ce->args[1]); if (expected.mode == Addressing_Invalid) return false; + check_expr(c, &timeout, ce->args[2]); if (timeout.mode == Addressing_Invalid) return false; + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + convert_to_typed(c, &ptr, t_u32_ptr); if (ptr.mode == Addressing_Invalid) return false; + convert_to_typed(c, &expected, t_u32); if (expected.mode == Addressing_Invalid) return false; + convert_to_typed(c, &timeout, t_i64); if (timeout.mode == Addressing_Invalid) return false; + + if (!is_operand_value(ptr) || !check_is_assignable_to(c, &ptr, t_u32_ptr)) { + gbString e = expr_to_string(ptr.expr); + gbString t = type_to_string(ptr.type); + error(ptr.expr, "'%.*s' expected ^u32 for the memory pointer, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(expected) || !check_is_assignable_to(c, &expected, t_u32)) { + gbString e = expr_to_string(expected.expr); + gbString t = type_to_string(expected.type); + error(expected.expr, "'%.*s' expected u32 for the 'expected' value, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(timeout) || !check_is_assignable_to(c, &timeout, t_i64)) { + gbString e = expr_to_string(timeout.expr); + gbString t = type_to_string(timeout.type); + error(timeout.expr, "'%.*s' expected i64 for the timeout, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_u32; + operand->value = {}; + break; + } + break; + case BuiltinProc_wasm_memory_atomic_notify32: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand ptr = {}; + Operand waiters = {}; + check_expr(c, &ptr, ce->args[0]); if (ptr.mode == Addressing_Invalid) return false; + check_expr(c, &waiters, ce->args[1]); if (waiters.mode == Addressing_Invalid) return false; + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + convert_to_typed(c, &ptr, t_u32_ptr); if (ptr.mode == Addressing_Invalid) return false; + convert_to_typed(c, &waiters, t_u32); if (waiters.mode == Addressing_Invalid) return false; + + if (!is_operand_value(ptr) || !check_is_assignable_to(c, &ptr, t_u32_ptr)) { + gbString e = expr_to_string(ptr.expr); + gbString t = type_to_string(ptr.type); + error(ptr.expr, "'%.*s' expected ^u32 for the memory pointer, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(waiters) || !check_is_assignable_to(c, &waiters, t_u32)) { + gbString e = expr_to_string(waiters.expr); + gbString t = type_to_string(waiters.type); + error(waiters.expr, "'%.*s' expected u32 for the 'waiters' value, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_u32; + operand->value = {}; + break; + } + break; + } return true; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index d301cae0c..d407ef7c1 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -218,6 +218,8 @@ BuiltinProc__type_end, BuiltinProc_wasm_memory_grow, BuiltinProc_wasm_memory_size, + BuiltinProc_wasm_memory_atomic_wait32, + BuiltinProc_wasm_memory_atomic_notify32, BuiltinProc_COUNT, }; @@ -438,4 +440,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("wasm_memory_grow"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("wasm_memory_size"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("wasm_memory_atomic_wait32"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("wasm_memory_atomic_notify32"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, }; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a7f9eb013..2539e6e2e 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2187,6 +2187,51 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + case BuiltinProc_wasm_memory_atomic_wait32: + { + char const *name = "llvm.wasm.memory.atomic.wait32"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_u32), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + + LLVMValueRef args[3] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32_ptr).value; + args[1] = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_u32).value; + args[2] = lb_emit_conv(p, lb_build_expr(p, ce->args[2]), t_i64).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + + case BuiltinProc_wasm_memory_atomic_notify32: + { + char const *name = "llvm.wasm.memory.atomic.notify"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_u32), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + + LLVMValueRef args[2] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32_ptr).value; + args[1] = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_u32).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + } GB_PANIC("Unhandled built-in procedure %.*s", LIT(builtin_procs[id].name)); diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 52fe43b6c..20ed7acfa 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1176,10 +1176,31 @@ class WebGLInterface { function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); + let currentLine = ""; + const addConsoleLine = (line) => { if (!line) { return; } + if (!line.includes("\n")) { + currentLine = currentLine.concat(line); + } else { + let printLast = line.endsWith("\n"); + let lines = line.split("\n"); + for (let i = 0; i < lines.length-1; i++) { + let theLine = lines[i].trim("\r"); + currentLine = currentLine.concat(line); + console.log(currentLine); + currentLine = ""; + } + console.log(lines); + if (printLast) { + console.log(lines[lines.length-1]); + } else { + currentLine = currentLine.concat(lines[lines.length-1]); + } + } + if (line.endsWith("\n")) { line = line.substring(0, line.length-1); } else if (infoConsoleLines.length > 0) { @@ -1191,16 +1212,15 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { infoConsoleLines.shift(); } - - let data = ""; - for (let i = 0; i < infoConsoleLines.length; i++) { - if (i != 0) { - data = data.concat("\n"); - } - data = data.concat(infoConsoleLines[i]); - } - if (consoleElement) { + let data = ""; + for (let i = 0; i < infoConsoleLines.length; i++) { + if (i != 0) { + data = data.concat("\n"); + } + data = data.concat(infoConsoleLines[i]); + } + let info = consoleElement; info.innerHTML = data; info.scrollTop = info.scrollHeight;