diff --git a/vlib/v/gen/fastc/render_method_calls.v b/vlib/v/gen/fastc/render_method_calls.v index c7ffce2d11..7a3f1239f3 100644 --- a/vlib/v/gen/fastc/render_method_calls.v +++ b/vlib/v/gen/fastc/render_method_calls.v @@ -477,7 +477,7 @@ fn (g &Parser) render_method_call_expression(tokens []FastcExpressionToken, rend } mut w := unsafe { &Parser(g) } fastc_register_composite_type(result_type, mut w.composite_types) - wait_helper := g.fastc_unclaimed_generated_name(fastc_thread_wait_name(flat.source_type_name(element))) + wait_helper := g.fastc_thread_wait_helper(flat.source_type_name(element), value_type) recv_src := if flat.source_type_name(receiver_type).ends_with('*') { '*(${receiver.source})' } else { @@ -517,7 +517,7 @@ fn (g &Parser) render_method_call_expression(tokens []FastcExpressionToken, rend wait_end := fastc_matching_rpar(tokens, i + 1) or { continue } receiver := g.render_method_receiver_expression(receiver_tokens) or { continue } value_type := (g.fastc_thread_value_type(receiver_type) or { u32(0) }) - wait_call := '${g.fastc_unclaimed_generated_name(fastc_thread_wait_name(flat.source_type_name(receiver_type)))}(${receiver.source})' + wait_call := '${g.fastc_thread_wait_helper(flat.source_type_name(receiver_type).trim_right('*'), value_type)}(${receiver.source})' if receiver_start == 0 && wait_end == tokens.len - 1 { return FastcRenderedExpression{ source: wait_call diff --git a/vlib/v/gen/fastc/spawn.v b/vlib/v/gen/fastc/spawn.v index d8816b6bb0..2f3dac9a01 100644 --- a/vlib/v/gen/fastc/spawn.v +++ b/vlib/v/gen/fastc/spawn.v @@ -97,6 +97,18 @@ fn fastc_thread_wait_name(thread_type string) string { return '__vf_thread_wait_${thread_type.all_after(fastc_thread_type_prefix)}' } +// fastc_thread_wait_helper returns the waiter of one thread handle type, +// registering its typedef and waiter when no `spawn` has done so yet. +fn (g &Parser) fastc_thread_wait_helper(thread_type string, value_type_source_id u32) string { + wait_name := g.fastc_unclaimed_generated_name(fastc_thread_wait_name(thread_type)) + if thread_type in g.spawn_typedefs && wait_name in g.spawn_helpers { + return wait_name + } + // Rendering keeps an immutable parser; the helper maps are per-file output. + mut w := unsafe { &Parser(g) } + return w.register_thread_type_helpers(thread_type, value_type_source_id) +} + // read_spawn_expression parses `spawn callee(arguments)` eagerly at an // expression start. The callee must be a collected plain or imported // function; its signature supplies the packed-argument field types. @@ -390,11 +402,10 @@ fn (g &Parser) generated_name_is_claimed(candidate string) bool { return false } -// register_spawn_helpers records the thread typedef, waiter, packed-argument -// struct, run wrapper, and creator for one spawn target. The result is the -// first packed field, so the per-type waiter reads it without knowing which -// call site produced the thread. -fn (mut g Parser) register_spawn_helpers(function_key string, thread_type string, value_type_source_id u32, parameter_types []u32, start_name string, target_c_name string) { +// register_thread_type_helpers records the typedef and waiter of one thread +// handle type, and returns the waiter name. `.wait()` calls register them too: +// a handle can be waited on in code whose only `spawn` is compiled out by `$if`. +fn (mut g Parser) register_thread_type_helpers(thread_type string, value_type_source_id u32) string { value_type := flat.source_type_name(value_type_source_id) g.type_memo.clear() g.thread_value_types.set_id(thread_type, value_type_source_id) @@ -430,6 +441,16 @@ fn (mut g Parser) register_spawn_helpers(function_key string, thread_type string } g.spawn_helpers[wait_name] = waiter } + return wait_name +} + +// register_spawn_helpers records the thread typedef, waiter, packed-argument +// struct, run wrapper, and creator for one spawn target. The result is the +// first packed field, so the per-type waiter reads it without knowing which +// call site produced the thread. +fn (mut g Parser) register_spawn_helpers(function_key string, thread_type string, value_type_source_id u32, parameter_types []u32, start_name string, target_c_name string) { + value_type := flat.source_type_name(value_type_source_id) + g.register_thread_type_helpers(thread_type, value_type_source_id) if start_name in g.spawn_helpers { return } diff --git a/vlib/v/gen/fastc/thread_array_runtime_test.v b/vlib/v/gen/fastc/thread_array_runtime_test.v index c1f0d3a336..345db0324c 100644 --- a/vlib/v/gen/fastc/thread_array_runtime_test.v +++ b/vlib/v/gen/fastc/thread_array_runtime_test.v @@ -55,3 +55,48 @@ fn main() { assert run.exit_code == 0, run.output assert run.output.trim_space() == 'thread arrays work', run.output } + +fn test_fastc_waits_on_thread_handles_whose_spawn_is_compiled_out() { + $if !macos && !linux { + return + } + vroot := os.real_path(os.join_path(@DIR, '../../../..')) + root := os.join_path(os.vtmp_dir(), 'fastc_thread_compiled_out_spawn_${os.getpid()}') + os.mkdir_all(root) or { panic(err) } + defer { os.rmdir_all(root) or {} } + // The driver waits on prestarted workers that only macOS spawns; the + // thread typedefs and waiters must exist without any reachable `spawn`. + os.write_file(os.join_path(root, 'main.v'), r'module main +struct Item { value int } +fn item(value int) Item { return Item{value: value} } +fn integer(value int) int { return value } +fn nothing() {} +fn main() { + mut items := []thread Item{} + mut integers := []thread int{} + mut completed := []thread{} + $if fastc_thread_never ? { + items << spawn item(1) + integers << spawn integer(2) + completed << spawn nothing() + } + if items.len > 0 { + assert items[0].wait().value == 1 + } + values := integers.wait() + completed.wait() + println("waited ${items.len} ${values.len} ${completed.len}") +} +') or { panic(err) } + program := os.join_path(root, 'program') + build := cmdexec.run_in_merged_with_env(@VEXE, ['-b', 'fastc', '-nocache', '-gc', 'none', '-o', + program, root], vroot, { + 'VEXE': @VEXE + 'VFLAGS': '' + 'V_MACOS_V3_NO_FALLBACK': '1' + }) + assert build.exit_code == 0, build.output + run := cmdexec.run(program, []) + assert run.exit_code == 0, run.output + assert run.output.trim_space() == 'waited 0 0 0', run.output +}