From 3d22ea23345653539242b4831d2524b7eaee27fe Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 30 Jun 2026 12:10:31 +0300 Subject: [PATCH] cgen, coroutines: fix `go expr` handle referencing undeclared `thread__tN` (#27379) --- examples/coroutines/go_handle.v | 24 ++++++++++++++++++++++++ vlib/v/gen/c/spawn_and_go.v | 15 +++++++++++++-- vlib/v/markused/walker.v | 14 ++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 examples/coroutines/go_handle.v diff --git a/examples/coroutines/go_handle.v b/examples/coroutines/go_handle.v new file mode 100644 index 0000000000..05b089c2ef --- /dev/null +++ b/examples/coroutines/go_handle.v @@ -0,0 +1,24 @@ +// vtest build: false // This should be built with: `v -use-coroutines go_handle.v` +// Note: the Photon wrapper is not yet trivial enough to build/install on the CI. +// +// Demonstrates storing the handle returned by a `go` expression and waiting on +// it. Because the Photon coroutine wrapper exposes no joinable handle, a `go` +// whose handle is used is lowered to the regular `spawn` (pthread) path, so the +// handle can be waited on just like `spawn`. +module main + +struct App { +mut: + worker thread +} + +fn (a App) loop() { + println('hello from the worker coroutine') +} + +fn main() { + mut a := App{} + a.worker = go a.loop() + a.worker.wait() + println('done') +} diff --git a/vlib/v/gen/c/spawn_and_go.v b/vlib/v/gen/c/spawn_and_go.v index 5a14e9ae02..b0c18a3958 100644 --- a/vlib/v/gen/c/spawn_and_go.v +++ b/vlib/v/gen/c/spawn_and_go.v @@ -15,8 +15,19 @@ fn (mut g Gen) spawn_and_go_expr(node ast.SpawnExpr, mode SpawnGoMode) { if node.call_expr.should_be_skipped { return } - is_spawn := mode == .spawn_ - is_go := mode == .go_ + mut is_spawn := mode == .spawn_ + mut is_go := mode == .go_ + if is_go && node.is_expr { + // A `go expr` whose handle is used as a value (e.g. `h := go f()` or + // `obj.field = go f()`) needs a real, joinable thread handle. The photon + // coroutine wrapper (`photon_thread_create*`) returns void and exposes no + // such handle, so fall back to the regular `spawn` (pthread) path here, + // which declares and assigns the `thread_` handle that is read back + // as the expression's value. Plain statement form `go f()` (no handle + // used) keeps using the photon work-pool path below. + is_spawn = true + is_go = false + } if is_spawn { g.writeln('/*spawn (thread) */') } else { diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 8d439e2b1a..bf3aa86d61 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -1079,6 +1079,20 @@ fn (mut w Walker) expr(node_ ast.Expr) { ast.GoExpr { if node.is_expr { w.fn_by_name('free') + // A `go expr` whose handle is used as a value is lowered to the + // `spawn` (pthread) codegen path (see spawn_and_go_expr), since the + // photon coroutine wrapper returns no joinable handle. Mark the same + // thread-handle type and pthread error helpers that `spawn` needs, + // otherwise -skip-unused prunes them and the generated C fails to link. + w.mark_by_type(w.table.find_or_register_thread(node.call_expr.return_type)) + w.uses_spawn = true + if w.pref.os == .windows { + w.fn_by_name('panic_lasterr') + w.fn_by_name('winapi_lasterr_str') + } else { + w.fn_by_name('c_error_number_str') + w.fn_by_name('panic_error_number') + } } w.mark_by_type(node.call_expr.return_type) w.expr(node.call_expr)