Browse Source

Begin work on Atomics for wasm32 (wait and notify intrinsics)

gingerBill 3 years ago
parent
commit
e48f41165c

+ 9 - 0
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{}

+ 36 - 0
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
+		}
+	}
+}
+

+ 8 - 0
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
+}

+ 93 - 0
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;

+ 4 - 0
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},
 };

+ 45 - 0
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));

+ 28 - 8
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");
+		if (consoleElement) {
+			let data = "";
+			for (let i = 0; i < infoConsoleLines.length; i++) {
+				if (i != 0) {
+					data = data.concat("\n");
+				}
+				data = data.concat(infoConsoleLines[i]);
 			}
-			data = data.concat(infoConsoleLines[i]);
-		}
 
-		if (consoleElement) {
 			let info = consoleElement;
 			info.innerHTML = data;
 			info.scrollTop = info.scrollHeight;