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)
 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)
 	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)
 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)
 	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)
 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)
 	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)
 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)
 	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
 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) {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T))t.data
 		fn := cast(proc(T))t.data
 		assert(t.user_index >= 1)
 		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 := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.data = rawptr(fn)
 	t.user_index = 1
 	t.user_index = 1
+
 	data := data
 	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 {
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 		t.flags += {.Self_Cleanup}
 	}
 	}
+
 	t.init_context = init_context
 	t.init_context = init_context
 	start(t)
 	start(t)
 	return 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
 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) {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T1, T2))t.data
 		fn := cast(proc(T1, T2))t.data
 		assert(t.user_index >= 2)
 		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)
 		fn(arg1, arg2)
 	}
 	}
 	t := create(thread_proc, priority)
 	t := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.data = rawptr(fn)
 	t.user_index = 2
 	t.user_index = 2
+
 	arg1, arg2 := arg1, arg2
 	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 {
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 		t.flags += {.Self_Cleanup}
 	}
 	}
+
 	t.init_context = init_context
 	t.init_context = init_context
 	start(t)
 	start(t)
 	return 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
 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) {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T1, T2, T3))t.data
 		fn := cast(proc(T1, T2, T3))t.data
 		assert(t.user_index >= 3)
 		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)
 		fn(arg1, arg2, arg3)
 	}
 	}
 	t := create(thread_proc, priority)
 	t := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.data = rawptr(fn)
 	t.user_index = 3
 	t.user_index = 3
+
 	arg1, arg2, arg3 := arg1, arg2, arg3
 	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 {
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 		t.flags += {.Self_Cleanup}
 	}
 	}
+
 	t.init_context = init_context
 	t.init_context = init_context
 	start(t)
 	start(t)
 	return 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
 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) {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(T1, T2, T3, T4))t.data
 		fn := cast(proc(T1, T2, T3, T4))t.data
 		assert(t.user_index >= 4)
 		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)
 		fn(arg1, arg2, arg3, arg4)
 	}
 	}
 	t := create(thread_proc, priority)
 	t := create(thread_proc, priority)
 	t.data = rawptr(fn)
 	t.data = rawptr(fn)
 	t.user_index = 4
 	t.user_index = 4
+
 	arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4
 	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 {
 	if self_cleanup {
 		t.flags += {.Self_Cleanup}
 		t.flags += {.Self_Cleanup}
 	}
 	}
+
 	t.init_context = init_context
 	t.init_context = init_context
 	start(t)
 	start(t)
 	return t
 	return t
 }
 }
 
 
-
 _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runtime.Context {
 _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runtime.Context {
 	ctx, ok := init_context.?
 	ctx, ok := init_context.?
 	if !ok {
 	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 \
 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 \
 	 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:
 download_test_assets:
 	$(PYTHON) download_assets.py
 	$(PYTHON) download_assets.py
@@ -61,3 +61,6 @@ net_test:
 
 
 fmt_test:
 fmt_test:
 	$(ODIN) run fmt -out:test_core_fmt
 	$(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 Running core:container tests
 echo ---
 echo ---
 %PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b
 %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)
+}