Browse Source

Allow larger thread poly data

The poly data currently has the restriction of being less than a
pointer's size, but there is much more space in the `Thread.user_args`
array which can be utilized, this commit allows you to pass types that are
larger than pointer length as long as the total size of the poly data is
less than that of the `Thread.user_args`.
Laytan Laats 1 year ago
parent
commit
9078ddaf5a
4 changed files with 144 additions and 39 deletions
  1. 55 38
      core/thread/thread.odin
  2. 4 1
      tests/core/Makefile
  3. 5 0
      tests/core/build.bat
  4. 80 0
      tests/core/thread/test_core_thread.odin

+ 55 - 38
core/thread/thread.odin

@@ -116,26 +116,21 @@ run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(
 }
 
 run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
-	where size_of(T) <= size_of(rawptr) {
+	where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data(data, fn, init_context, priority, true)
 }
 
 run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
-	where size_of(T1) <= size_of(rawptr),
-	      size_of(T2) <= size_of(rawptr) {
+	where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true)
 }
 
 run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
-	where size_of(T1) <= size_of(rawptr),
-	      size_of(T2) <= size_of(rawptr),
-	      size_of(T3) <= size_of(rawptr) {
+	where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true)
 }
 run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
-	where size_of(T1) <= size_of(rawptr),
-	      size_of(T2) <= size_of(rawptr),
-	      size_of(T3) <= size_of(rawptr) {
+	where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true)
 }
 
@@ -178,7 +173,7 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co
 }
 
 create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
-	where size_of(T) <= size_of(rawptr) {
+	where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T))t.data
 		assert(t.user_index >= 1)
@@ -188,96 +183,118 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex
 	t := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.user_index = 1
+
 	data := data
-	mem.copy(&t.user_args[0], &data, size_of(data))
+
+	mem.copy(&t.user_args[0], &data, size_of(T))
+
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 	}
+
 	t.init_context = init_context
 	start(t)
 	return t
 }
 
 create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
-	where size_of(T1) <= size_of(rawptr),
-	      size_of(T2) <= size_of(rawptr) {
+	where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T1, T2))t.data
 		assert(t.user_index >= 2)
-		arg1 := (^T1)(&t.user_args[0])^
-		arg2 := (^T2)(&t.user_args[1])^
+		
+		user_args := mem.slice_to_bytes(t.user_args[:])
+		arg1 := (^T1)(raw_data(user_args))^
+		arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
+
 		fn(arg1, arg2)
 	}
 	t := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.user_index = 2
+
 	arg1, arg2 := arg1, arg2
-	mem.copy(&t.user_args[0], &arg1, size_of(arg1))
-	mem.copy(&t.user_args[1], &arg2, size_of(arg2))
+	user_args := mem.slice_to_bytes(t.user_args[:])
+
+	n := copy(user_args,     mem.ptr_to_bytes(&arg1))
+	_  = copy(user_args[n:], mem.ptr_to_bytes(&arg2))
+
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 	}
+
 	t.init_context = init_context
 	start(t)
 	return t
 }
 
 create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
-	where size_of(T1) <= size_of(rawptr),
-	      size_of(T2) <= size_of(rawptr),
-	      size_of(T3) <= size_of(rawptr) {
+	where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T1, T2, T3))t.data
 		assert(t.user_index >= 3)
-		arg1 := (^T1)(&t.user_args[0])^
-		arg2 := (^T2)(&t.user_args[1])^
-		arg3 := (^T3)(&t.user_args[2])^
+
+		user_args := mem.slice_to_bytes(t.user_args[:])
+		arg1 := (^T1)(raw_data(user_args))^
+		arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
+		arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^
+
 		fn(arg1, arg2, arg3)
 	}
 	t := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.user_index = 3
+
 	arg1, arg2, arg3 := arg1, arg2, arg3
-	mem.copy(&t.user_args[0], &arg1, size_of(arg1))
-	mem.copy(&t.user_args[1], &arg2, size_of(arg2))
-	mem.copy(&t.user_args[2], &arg3, size_of(arg3))
+	user_args := mem.slice_to_bytes(t.user_args[:])
+
+	n := copy(user_args,     mem.ptr_to_bytes(&arg1))
+	n += copy(user_args[n:], mem.ptr_to_bytes(&arg2))
+	_  = copy(user_args[n:], mem.ptr_to_bytes(&arg3))
+
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 	}
+
 	t.init_context = init_context
 	start(t)
 	return t
 }
 create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
-	where size_of(T1) <= size_of(rawptr),
-	      size_of(T2) <= size_of(rawptr),
-	      size_of(T3) <= size_of(rawptr) {
+	where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T1, T2, T3, T4))t.data
 		assert(t.user_index >= 4)
-		arg1 := (^T1)(&t.user_args[0])^
-		arg2 := (^T2)(&t.user_args[1])^
-		arg3 := (^T3)(&t.user_args[2])^
-		arg4 := (^T4)(&t.user_args[3])^
+
+		user_args := mem.slice_to_bytes(t.user_args[:])
+		arg1 := (^T1)(raw_data(user_args))^
+		arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
+		arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^
+		arg4 := (^T4)(raw_data(user_args[size_of(T1) + size_of(T2) + size_of(T3):]))^
+
 		fn(arg1, arg2, arg3, arg4)
 	}
 	t := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.user_index = 4
+
 	arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4
-	mem.copy(&t.user_args[0], &arg1, size_of(arg1))
-	mem.copy(&t.user_args[1], &arg2, size_of(arg2))
-	mem.copy(&t.user_args[2], &arg3, size_of(arg3))
-	mem.copy(&t.user_args[3], &arg4, size_of(arg4))
+	user_args := mem.slice_to_bytes(t.user_args[:])
+
+	n := copy(user_args,     mem.ptr_to_bytes(&arg1))
+	n += copy(user_args[n:], mem.ptr_to_bytes(&arg2))
+	n += copy(user_args[n:], mem.ptr_to_bytes(&arg3))
+	_  = copy(user_args[n:], mem.ptr_to_bytes(&arg4))
+
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 	}
+
 	t.init_context = init_context
 	start(t)
 	return t
 }
 
-
 _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runtime.Context {
 	ctx, ok := init_context.?
 	if !ok {

+ 4 - 1
tests/core/Makefile

@@ -3,7 +3,7 @@ PYTHON=$(shell which python3)
 
 all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \
 	 math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test i18n_test match_test c_libc_test net_test \
-	 fmt_test
+	 fmt_test thread_test
 
 download_test_assets:
 	$(PYTHON) download_assets.py
@@ -61,3 +61,6 @@ net_test:
 
 fmt_test:
 	$(ODIN) run fmt -out:test_core_fmt
+
+thread_test:
+	$(ODIN) run thread -out:test_core_thread

+ 5 - 0
tests/core/build.bat

@@ -85,3 +85,8 @@ echo ---
 echo Running core:container tests
 echo ---
 %PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b
+
+echo ---
+echo Running core:thread tests
+echo ---
+%PATH_TO_ODIN% run thread %COMMON% %COLLECTION% -out:test_core_thread.exe || exit /b

+ 80 - 0
tests/core/thread/test_core_thread.odin

@@ -0,0 +1,80 @@
+package test_core_thread
+
+import "core:testing"
+import "core:thread"
+import "core:fmt"
+import "core:os"
+
+TEST_count := 0
+TEST_fail  := 0
+
+t := &testing.T{}
+
+when ODIN_TEST {
+    expect  :: testing.expect
+    log     :: testing.log
+} else {
+    expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
+        TEST_count += 1
+        if !condition {
+            TEST_fail += 1
+            fmt.printf("[%v] %v\n", loc, message)
+            return
+        }
+    }
+    log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
+        fmt.printf("[%v] ", loc)
+        fmt.printf("log: %v\n", v)
+    }
+}
+
+main :: proc() {
+	poly_data_test(t)
+
+	if TEST_fail > 0 {
+		os.exit(1)
+	}
+}
+
+@(test)
+poly_data_test :: proc(_t: ^testing.T) {
+	MAX :: size_of(rawptr) * thread.MAX_USER_ARGUMENTS
+
+	@static t: ^testing.T
+	t = _t
+
+	b: [MAX]byte = 8
+	t1 := thread.create_and_start_with_poly_data(b, proc(b: [MAX]byte) {
+		b_expect: [MAX]byte = 8
+		expect(t, b == b_expect, "thread poly data not correct")
+	}, self_cleanup = true)
+	
+	b1: [3]uintptr = 1
+	b2: [MAX / 2]byte = 3
+	t2 := thread.create_and_start_with_poly_data2(b1, b2, proc(b: [3]uintptr, b2: [MAX / 2]byte) {
+		b_expect: [3]uintptr = 1
+		b2_expect: [MAX / 2]byte = 3
+		expect(t, b == b_expect,   "thread poly data not correct")
+		expect(t, b2 == b2_expect, "thread poly data not correct")
+	}, self_cleanup = true)
+
+	t3 := thread.create_and_start_with_poly_data3(b1, b2, uintptr(333), proc(b: [3]uintptr, b2: [MAX / 2]byte, b3: uintptr) {
+		b_expect: [3]uintptr = 1
+		b2_expect: [MAX / 2]byte = 3
+
+		expect(t, b == b_expect,   "thread poly data not correct")
+		expect(t, b2 == b2_expect, "thread poly data not correct")
+		expect(t, b3 == 333,       "thread poly data not correct")
+	}, self_cleanup = true)
+	
+	t4 := thread.create_and_start_with_poly_data4(uintptr(111), b1, uintptr(333), u8(5), proc(n: uintptr, b: [3]uintptr, n2: uintptr, n4: u8) {
+		b_expect: [3]uintptr = 1
+
+		expect(t, n == 111,        "thread poly data not correct")
+		expect(t, b == b_expect,   "thread poly data not correct")
+		expect(t, n2 == 333,       "thread poly data not correct")
+		expect(t, n4 == 5,         "thread poly data not correct")
+	}, self_cleanup = true)
+
+	thread.join_multiple(t1, t2, t3, t4)
+}