#5705·tinygo

`-scheduler=none` wasip1 build fails: time.goFunc kept live in whole-program graph (regression vs 0.41-era builds)

Author: Andrei-cloudCreated Sep 18, 2026Updated Sep 18, 2026

The bug

Building for -target=wasi -scheduler=none fails whenever a program uses any fmt stream-output call (fmt.Println, fmt.Printf, fmt.Fprint(os.Stdout), …):

$ tinygo build -target=wasi -scheduler=none -o repro.wasm .
/usr/local/go/src/time/sleep.go:182:2: attempted to start a goroutine without a scheduler

time.goFunc (the only body on that time/sleep.go line) is referenced only by time.AfterFunc, and this program never calls time.AfterFunc — nor any timer, time.Sleep, or goroutine. The reference comes from TinyGo's own WASI poll integration: $(TINYGOROOT)/src/internal/poll/fd_wasip1.go:542 uses time.AfterFunc(d, func() { … }) to wake parked goroutines on deadline writes (writeWithDeadline). Under scheduler=none the compiler keeps that callback live once fmt's interface-dispatched write path is retained, and the build aborts.

Minimal reproduction

Three files, no dependencies:

bash
mkdir wasi-fmt-repro && cd wasi-fmt-repro

go.mod:

module repro

go 1.24.0

main.go:

go
package main

import "fmt"

func main() { fmt.Println("x") }
tinygo build -target=wasi -scheduler=none -o repro.wasm .

Expected: a 0-byte repro.wasm. Actual: the error above. The module's go directive does not matter (reproduces at 1.24.0); the failure comes from the host Go 1.27 stdlib + TinyGo's internal/poll overlay.

Control matrix (same one-line programs, same flags)

main.go body Result
fmt.Println("x") FAILS
fmt.Printf("%s\n", "x") FAILS
fmt.Fprint(os.Stdout, "x") FAILS
fmt.Fprintln(os.Stdout, "x") FAILS
fmt.Fprintf(os.Stderr, "x\n") FAILS
os.Stdout.Write([]byte("x")) builds
os.Stderr.Write([]byte("x")) builds
log.Fatalf("e") (live behind a runtime-false branch) builds
fmt.Sprintf/fmt.Errorf/fmt.Sprint only builds
time.Now(), crypto/rand.Read, os.Args, os.Getenv only builds

The contrast is sharp and reproducible in one line each: os.Stdout.Write(...) compiles; fmt.Fprint(os.Stdout, ...) does not. Concrete-type calls to (*os.File).Write get eliminated; fmt's io.Writer interface dispatch retains writeWithDeadline → keeps time.AfterFunc → keeps goFunc's go statement → scheduler=none check trips.

Why this matters

  • scheduler=none is the default (and recommended) scheduler for the wasi target, and fmt.Println/fmt.Printf are the most common output idiom in Go. Today any such program simply cannot be built for WASI, with an error message that names neither the construct (fmt.Println) nor the path (fd_wasip1 deadline machinery) at fault.
  • This is a regression from the 0.41-era builds: the same sources built fine with the pre-0.42 toolchain; 0.42's wasip1 poll_oneoff scheduler integration (#5386) introduced the time.AfterFunc usage, and the 0.42.0 release notes themselves mention "skip flaky and Go 1.27 affected tests for now", suggesting scheduler=none WASI CI coverage may be missing.
  • Related scheduler=none foot-guns found while bisecting, in case they share the same root: fmt.Sscanf can also poison a build depending on the rest of the graph, and crypto/rand on wasip1 (no getrandom(2)) falls back to os.File reads which need the same deadline machinery — guests therefore cannot use it once the graph keeps os writers live.

Suggestions

  1. Make the error actionable: when scheduler=none trips, report which program construct imports the goroutine (a path like "fmt.Println → os.File.Write → poll fd deadline"), not just time/sleep.go:182.
  2. Prefer a scheduler-free wake mechanism inside fd_wasip1.go (e.g. rely on poll_oneoff's own timeout argument rather than time.AfterFunc), so plain fmt.Println programs link under scheduler=none.
  3. Add CI coverage for -target=wasi -scheduler=none with a program that uses fmt.Println and fmt.Sscanf.

Environment

  • TinyGo 0.42.0 (Homebrew, darwin-arm64, LLVM 22.1.4)
  • Host Go 1.27.1 (failure is the same with any module go directive tested: 1.24.0–1.27.1)
  • Command: tinygo build -target=wasi -scheduler=none -o repro.wasm .