Skip to content

Commit 39b8954

Browse files
runtime,internal/poll,loader: wasip2 pollable poll-integration + TCP I/O
Mirrors PR #5386's wasip1 work for wasip2. The cooperative scheduler's idle path now calls wasi:io/poll.Poll over a combined list of (clock pollable, registered pollables) instead of blocking the wasm module on a single monotonic-clock subscription, so goroutines doing TCP I/O can park while the scheduler runs other goroutines. Plumbing components: - runtime/netpoll_wasip2.go: pollable-keyed pollDesc registry; pollIO builds one combined wasi:io/poll.Poll call (clock pollable + active pollables). Linkname-exposed runtime_netpoll_addpollable_wasip2 / done / pdfired / wake for internal/poll and future net. - runtime/scheduler_idle_wasip2.go + scheduler_idle_wasip2_none.go: cooperative-variant sleepTicks / waitForEvents that route through pollIO; non-coop fallback uses monotonicclock.Block. Mirrors the wasip1 structure introduced in 7000e7b. - runtime/runtime_wasip2.go: sleepTicks moved out to the scheduler_idle_wasip2*.go files. - runtime/wait_other.go: build tag tightened to exclude wasip2. internal/poll surface: - internal/poll/fd_wasip2.go: WasipNFD wraps a (TcpSocket, InputStream, OutputStream) triple. DialTCPWasip2, ListenTCPWasip2, Accept, Read, Write, Close, SetDeadline*. Each blocking op tries the wasi call, on would-block subscribes, parks, retries — same pattern as the wasip1 internal/poll.FD but pollable-keyed. Linkname-friendly Wasip2TCP{Listen,Dial,Accept,Read,Write,Close,SetDeadline} wrappers for test / future net callers. - internal/poll/errors_wasip.go: ErrFileClosing / ErrNetClosing / ErrDeadlineExceeded / ErrNoDeadline extracted from fd_wasip1.go to a wasip1||wasip2 shared file. Loader change: - loader/goroot.go: listGorootMergeLinks now filters TinyGo files by //go:build constraints (via go/build.Context.MatchFile) before deciding "TinyGo owns this directory". Files that don't match the current target no longer cause upstream Go files at the same level to be dropped. Unblocks per-target overrides in directories like src/net/ for future net.wasip2 work without disturbing wasip1. End-to-end verification: $ wasmtime run -Sinherit-network -Stcp ./tcpecho_wasip2.wasm & listening on 127.0.0.1:9999 tick 1 tick 2 tick 3 $ echo hello | nc 127.0.0.1 9999 hello # echoed by the wasm $ # two concurrent clients echo cleanly while ticker keeps ticking The test program (not shipped) uses //go:linkname to drive the internal/poll TCP helpers directly, since TinyGo doesn't yet have a net.Listen / net.Dial path on wasip2 (upstream Go's net doesn't build for wasip2 due to cgo_linux.go reaching for Linux headers). The src/net/ wasip2 wrappers are out of scope for this PR and tracked as follow-up — once they land, callers will use net.Listen / Dial directly and the linkname wrappers can drop. Wasip1 regression sweep: tcpecho.wasm still passes; time.Sleep / parkfile / parksynth unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 81b2957 commit 39b8954

10 files changed

Lines changed: 823 additions & 23 deletions

loader/goroot.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"encoding/hex"
1717
"encoding/json"
1818
"errors"
19+
"go/build"
1920
"io"
2021
"io/fs"
2122
"os"
@@ -48,7 +49,7 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
4849
overrides := pathsToOverride(config.GoMinorVersion, needsSyscallPackage(config.BuildTags()))
4950

5051
// Resolve the merge links within the goroot.
51-
merge, err := listGorootMergeLinks(goroot, tinygoroot, overrides)
52+
merge, err := listGorootMergeLinks(goroot, tinygoroot, overrides, config)
5253
if err != nil {
5354
return "", err
5455
}
@@ -143,10 +144,22 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
143144
}
144145

145146
// listGorootMergeLinks searches goroot and tinygoroot for all symlinks that must be created within the merged goroot.
146-
func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) (map[string]string, error) {
147+
func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool, config *compileopts.Config) (map[string]string, error) {
147148
goSrc := filepath.Join(goroot, "src")
148149
tinygoSrc := filepath.Join(tinygoroot, "src")
149150
merges := make(map[string]string)
151+
152+
// buildContext is used to evaluate //go:build constraints on TinyGo
153+
// source files. A TinyGo file that doesn't match the current target
154+
// (e.g. a *_wasip2.go file in a wasip1 build) must not be treated as
155+
// "TinyGo owns this directory" — otherwise wasip1 builds would lose
156+
// upstream Go files at the same level.
157+
bctx := build.Default
158+
bctx.GOOS = config.GOOS()
159+
bctx.GOARCH = config.GOARCH()
160+
bctx.BuildTags = config.BuildTags()
161+
bctx.Compiler = "gc"
162+
150163
for dir, merge := range overrides {
151164
if !merge {
152165
// Use the TinyGo version.
@@ -168,6 +181,14 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool)
168181

169182
// Link this file.
170183
name := e.Name()
184+
// Only count this file as a TinyGo override of the directory
185+
// when its build tags match the current target. Files with a
186+
// non-matching //go:build are invisible to this build anyway
187+
// (the compiler would skip them), so they shouldn't cause
188+
// upstream files to be dropped from the merge.
189+
if matched, _ := bctx.MatchFile(tinygoDir, name); !matched {
190+
continue
191+
}
171192
merges[filepath.Join("src", dir, name)] = filepath.Join(tinygoDir, name)
172193

173194
hasTinyGoFiles = true

src/internal/poll/errors_wasip.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//go:build wasip1 || wasip2
2+
3+
// Shared error sentinels for the wasip1 and wasip2 internal/poll
4+
// implementations. The error values are part of the public API surface
5+
// upstream net relies on; sharing them keeps fd_wasip1.go and
6+
// fd_wasip2.go free of redundant declarations.
7+
8+
package poll
9+
10+
import "errors"
11+
12+
// ErrFileClosing is returned when a Read or Write is started on a closed FD.
13+
var ErrFileClosing = errors.New("use of closed file")
14+
15+
// ErrNetClosing is returned for network operations on a closed FD.
16+
var ErrNetClosing = errors.New("use of closed network connection")
17+
18+
// ErrDeadlineExceeded is returned by Read/Write when a deadline expired.
19+
// Matches the error returned by os.IsTimeout-style helpers.
20+
var ErrDeadlineExceeded = errors.New("i/o timeout")
21+
22+
// ErrNoDeadline is returned if SetDeadline is called on an FD whose
23+
// underlying type does not support deadlines.
24+
var ErrNoDeadline = errors.New("file type does not support deadline")

src/internal/poll/fd_wasip1.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,12 @@
2020
package poll
2121

2222
import (
23-
"errors"
2423
"internal/task"
2524
"syscall"
2625
"time"
2726
"unsafe"
2827
)
2928

30-
// ErrFileClosing is returned when a Read or Write is started on a closed FD.
31-
var ErrFileClosing = errors.New("use of closed file")
32-
33-
// ErrNetClosing is returned for network operations on a closed FD.
34-
var ErrNetClosing = errors.New("use of closed network connection")
35-
36-
// ErrDeadlineExceeded is returned by Read/Write when a deadline expired.
37-
// Matches the error returned by os.IsTimeout-style helpers.
38-
var ErrDeadlineExceeded = errors.New("i/o timeout")
39-
40-
// ErrNoDeadline is returned if SetDeadline is called on an FD whose
41-
// underlying type does not support deadlines.
42-
var ErrNoDeadline = errors.New("file type does not support deadline")
43-
4429
// pollMode constants must mirror runtime/netpoll_wasip1.go's pollRead/
4530
// pollWrite values.
4631
const (

0 commit comments

Comments
 (0)