Repository: tetratelabs/wazero
Pull request #1613 (2023-08-07 00:21:31)

Re-introduces internal fsapi.File with non-blocking methods
Folks aren't currently on the same page on whether or not users should be able to implement the timeout parameter on `File.Poll` or whether it should always be externally controlled via a sleep loop which uses poll (immediate). Until we are sure internally, we shouldn't expose this for implementation, as it would be confusing for the same reason.

This migrates the non-blocking functionality back into the internal package.

One future way out could be to allow the user to decide if the backend is allowed to use the timeout parameter or not. Just as they control the FS, they could control a poller, which would then be able to span across both normal files and sockets. One could then be able to choose in the case of a single file and a specific known wasm, simply use the native timeout. In other words, since there are valid alternate answers, even though Go doesn't support custom pollers we could, if we decide that native polling is basically not allowed. Alternatively, we could force it to never be allowed by removing the timeout parameter (making it a poll immediate). Anyway, I will leave that decision up to @ncruces and @evacchi who will take responsibility of whichever decision is made.

codefromthecrypt: the main impact of this change is now you cannot really wrap all functions used by wasi anymore. However, it is still better than not exposing the ones that are stable. I wasn't aware the File.Poll interface wasn't ready until #1606 which sadly happened *after* I promised publicly we would release our file api. 🤷 I tried..

Patch:
diff --git a/Makefile b/Makefile
index 900e88cd..4ed46536 100644
--- a/Makefile
+++ b/Makefile
@@ -214,6 +214,8 @@ check:
 #
 # Ensure we build on plan9. See #1578
 	@GOARCH=amd64 GOOS=plan9 go build ./...
+# Ensure we build on gojs. See #1526. TODO: add GOOS=wasi as well.
+	@GOARCH=wasm GOOS=js go build ./...
 # Ensure we build on windows:
 	@GOARCH=amd64 GOOS=windows go build ./...
 # Ensure we build on an arbitrary operating system:
diff --git a/experimental/sys/dir.go b/experimental/sys/dir.go
index 047e5daa..0b997cb8 100644
--- a/experimental/sys/dir.go
+++ b/experimental/sys/dir.go
@@ -61,16 +61,6 @@ func (DirFile) SetAppend(bool) Errno {
 	return EISDIR
 }
 
-// IsNonblock implements File.IsNonblock
-func (DirFile) IsNonblock() bool {
-	return false
-}
-
-// SetNonblock implements File.SetNonblock
-func (DirFile) SetNonblock(bool) Errno {
-	return EISDIR
-}
-
 // IsDir implements File.IsDir
 func (DirFile) IsDir() (bool, Errno) {
 	return true, 0
@@ -86,11 +76,6 @@ func (DirFile) Pread([]byte, int64) (int, Errno) {
 	return 0, EISDIR
 }
 
-// Poll implements File.Poll
-func (DirFile) Poll(Pflag, int32) (ready bool, errno Errno) {
-	return false, ENOSYS
-}
-
 // Write implements File.Write
 func (DirFile) Write([]byte) (int, Errno) {
 	return 0, EISDIR
diff --git a/experimental/sys/file.go b/experimental/sys/file.go
index d1abfc6a..f8f2e5b1 100644
--- a/experimental/sys/file.go
+++ b/experimental/sys/file.go
@@ -67,29 +67,6 @@ type File interface {
 	//   - Implementations should cache this result.
 	IsDir() (bool, Errno)
 
-	// IsNonblock returns true if the file was opened with O_NONBLOCK, or
-	// SetNonblock was successfully enabled on this file.
-	//
-	// # Notes
-	//
-	//   - This might not match the underlying state of the file descriptor if
-	//     the file was not opened via OpenFile.
-	IsNonblock() bool
-
-	// SetNonblock toggles the non-blocking mode (O_NONBLOCK) of this file.
-	//
-	// # Errors
-	//
-	// A zero Errno is success. The below are expected otherwise:
-	//   - ENOSYS: the implementation does not support this function.
-	//   - EBADF: the file or directory was closed.
-	//
-	// # Notes
-	//
-	//   - This is like syscall.SetNonblock and `fcntl` with O_NONBLOCK in
-	//     POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
-	SetNonblock(enable bool) Errno
-
 	// IsAppend returns true if the file was opened with O_APPEND, or
 	// SetAppend was successfully enabled on this file.
 	//
@@ -200,37 +177,6 @@ type File interface {
 	//     of io.Seeker. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html
 	Seek(offset int64, whence int) (newOffset int64, errno Errno)
 
-	// Poll returns if the file has data ready to be read or written.
-	//
-	// # Parameters
-	//
-	// The `flag` parameter determines which event to await, such as POLLIN,
-	// POLLOUT, or a combination like `POLLIN|POLLOUT`.
-	//
-	// The `timeoutMillis` parameter is how long to block for an event, or
-	// interrupted, in milliseconds. There are two special values:
-	//   - zero returns immediately
-	//   - any negative value blocks any amount of time
-	//
-	// # Results
-	//
-	// `ready` means there was data ready to read or written. False can mean no
-	// event was ready or `errno` is not zero.
-	//
-	// A zero `errno` is success. The below are expected otherwise:
-	//   - ENOSYS: the implementation does not support this function.
-	//   - ENOTSUP: the implementation does not the flag combination.
-	//   - EINTR: the call was interrupted prior to an event.
-	//
-	// # Notes
-	//
-	//   - This is like `poll` in POSIX, for a single file.
-	//     See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html
-	//   - No-op files, such as those which read from /dev/null, should return
-	//     immediately true, as data will never become available.
-	//   - See /RATIONALE.md for detailed notes including impact of blocking.
-	Poll(flag Pflag, timeoutMillis int32) (ready bool, errno Errno)
-
 	// Readdir reads the contents of the directory associated with file and
 	// returns a slice of up to n Dirent values in an arbitrary order. This is
 	// a stateful function, so subsequent calls return any next values.
diff --git a/experimental/sys/unimplemented.go b/experimental/sys/unimplemented.go
index 764fba6a..d853d9e8 100644
--- a/experimental/sys/unimplemented.go
+++ b/experimental/sys/unimplemented.go
@@ -101,16 +101,6 @@ func (UnimplementedFile) SetAppend(bool) Errno {
 	return ENOSYS
 }
 
-// IsNonblock implements File.IsNonblock
-func (UnimplementedFile) IsNonblock() bool {
-	return false
-}
-
-// SetNonblock implements File.SetNonblock
-func (UnimplementedFile) SetNonblock(bool) Errno {
-	return ENOSYS
-}
-
 // Stat implements File.Stat
 func (UnimplementedFile) Stat() (sys.Stat_t, Errno) {
 	return sys.Stat_t{}, ENOSYS
@@ -136,11 +126,6 @@ func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno Errno) {
 	return nil, ENOSYS
 }
 
-// Poll implements File.Poll
-func (UnimplementedFile) Poll(Pflag, int32) (ready bool, errno Errno) {
-	return false, ENOSYS
-}
-
 // Write implements File.Write
 func (UnimplementedFile) Write([]byte) (int, Errno) {
 	return 0, ENOSYS
diff --git a/imports/wasi_snapshot_preview1/poll.go b/imports/wasi_snapshot_preview1/poll.go
index a3eca018..d09f3024 100644
--- a/imports/wasi_snapshot_preview1/poll.go
+++ b/imports/wasi_snapshot_preview1/poll.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/wasip1"
 	"github.com/tetratelabs/wazero/internal/wasm"
@@ -177,7 +178,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno
 	}
 	// Wait for the timeout to expire, or for some data to become available on Stdin.
 
-	if stdinReady, errno := stdin.File.Poll(sys.POLLIN, int32(timeout.Milliseconds())); errno != 0 {
+	if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 {
 		return errno
 	} else if stdinReady {
 		// stdin has data ready to for reading, write back all the events
diff --git a/internal/fsapi/file.go b/internal/fsapi/file.go
new file mode 100644
index 00000000..0640b227
--- /dev/null
+++ b/internal/fsapi/file.go
@@ -0,0 +1,69 @@
+package fsapi
+
+import experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+
+// File includes methods not yet ready to document for end users, notably
+// non-blocking functionality.
+//
+// Particularly, Poll is subject to debate. For example, whether a user should
+// be able to choose how to implement timeout or not. Currently, this interface
+// allows the user to choose to sleep or use native polling, and which choice
+// they make impacts thread behavior as summarized here:
+// https://github.com/tetratelabs/wazero/pull/1606#issuecomment-1665475516
+type File interface {
+	experimentalsys.File
+
+	// IsNonblock returns true if the file was opened with O_NONBLOCK, or
+	// SetNonblock was successfully enabled on this file.
+	//
+	// # Notes
+	//
+	//   - This might not match the underlying state of the file descriptor if
+	//     the file was not opened via OpenFile.
+	IsNonblock() bool
+
+	// SetNonblock toggles the non-blocking mode (O_NONBLOCK) of this file.
+	//
+	// # Errors
+	//
+	// A zero Errno is success. The below are expected otherwise:
+	//   - ENOSYS: the implementation does not support this function.
+	//   - EBADF: the file or directory was closed.
+	//
+	// # Notes
+	//
+	//   - This is like syscall.SetNonblock and `fcntl` with O_NONBLOCK in
+	//     POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+	SetNonblock(enable bool) experimentalsys.Errno
+
+	// Poll returns if the file has data ready to be read or written.
+	//
+	// # Parameters
+	//
+	// The `flag` parameter determines which event to await, such as POLLIN,
+	// POLLOUT, or a combination like `POLLIN|POLLOUT`.
+	//
+	// The `timeoutMillis` parameter is how long to block for an event, or
+	// interrupted, in milliseconds. There are two special values:
+	//   - zero returns immediately
+	//   - any negative value blocks any amount of time
+	//
+	// # Results
+	//
+	// `ready` means there was data ready to read or written. False can mean no
+	// event was ready or `errno` is not zero.
+	//
+	// A zero `errno` is success. The below are expected otherwise:
+	//   - ENOSYS: the implementation does not support this function.
+	//   - ENOTSUP: the implementation does not the flag combination.
+	//   - EINTR: the call was interrupted prior to an event.
+	//
+	// # Notes
+	//
+	//   - This is like `poll` in POSIX, for a single file.
+	//     See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html
+	//   - No-op files, such as those which read from /dev/null, should return
+	//     immediately true, as data will never become available.
+	//   - See /RATIONALE.md for detailed notes including impact of blocking.
+	Poll(flag Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno)
+}
diff --git a/experimental/sys/poll.go b/internal/fsapi/poll.go
similarity index 97%
rename from experimental/sys/poll.go
rename to internal/fsapi/poll.go
index c63e55b9..25f7c571 100644
--- a/experimental/sys/poll.go
+++ b/internal/fsapi/poll.go
@@ -1,4 +1,4 @@
-package sys
+package fsapi
 
 // Pflag are bit flags used for File.Poll. Values, including zero, should not
 // be interpreted numerically. Instead, use by constants prefixed with 'POLL'.
diff --git a/internal/fsapi/unimplemented.go b/internal/fsapi/unimplemented.go
new file mode 100644
index 00000000..99d9c2db
--- /dev/null
+++ b/internal/fsapi/unimplemented.go
@@ -0,0 +1,27 @@
+package fsapi
+
+import experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+
+func Adapt(f experimentalsys.File) File {
+	if f, ok := f.(File); ok {
+		return f
+	}
+	return unimplementedFile{f}
+}
+
+type unimplementedFile struct{ experimentalsys.File }
+
+// IsNonblock implements File.IsNonblock
+func (unimplementedFile) IsNonblock() bool {
+	return false
+}
+
+// SetNonblock implements File.SetNonblock
+func (unimplementedFile) SetNonblock(bool) experimentalsys.Errno {
+	return experimentalsys.ENOSYS
+}
+
+// Poll implements File.Poll
+func (unimplementedFile) Poll(Pflag, int32) (ready bool, errno experimentalsys.Errno) {
+	return false, experimentalsys.ENOSYS
+}
diff --git a/internal/sock/sock_plan9.go b/internal/sock/sock_plan9.go
deleted file mode 100644
index b00a743f..00000000
--- a/internal/sock/sock_plan9.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package sock
-
-// plan9 doesn't declare these constants
-const (
-	SHUT_RD = iota
-	SHUT_RDWR
-	SHUT_WR
-)
diff --git a/internal/sock/sock_supported.go b/internal/sock/sock_supported.go
index a35376d8..30cdb9f9 100644
--- a/internal/sock/sock_supported.go
+++ b/internal/sock/sock_supported.go
@@ -1,4 +1,4 @@
-//go:build !plan9
+//go:build !plan9 && !js
 
 package sock
 
diff --git a/internal/sock/sock_unsupported.go b/internal/sock/sock_unsupported.go
new file mode 100644
index 00000000..76ec031e
--- /dev/null
+++ b/internal/sock/sock_unsupported.go
@@ -0,0 +1,10 @@
+//go:build plan9 || js
+
+package sock
+
+// plan9/js doesn't declare these constants
+const (
+	SHUT_RD = 1 << iota
+	SHUT_WR
+	SHUT_RDWR = SHUT_RD | SHUT_WR
+)
diff --git a/internal/sys/fs.go b/internal/sys/fs.go
index 2d1ce519..332a9526 100644
--- a/internal/sys/fs.go
+++ b/internal/sys/fs.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/tetratelabs/wazero/experimental/sys"
 	"github.com/tetratelabs/wazero/internal/descriptor"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	socketapi "github.com/tetratelabs/wazero/internal/sock"
 	"github.com/tetratelabs/wazero/internal/sysfs"
 )
@@ -50,7 +51,7 @@ type FileEntry struct {
 	FS sys.FS
 
 	// File is always non-nil.
-	File sys.File
+	File fsapi.File
 
 	// direntCache is nil until DirentCache was called.
 	direntCache *DirentCache
@@ -287,7 +288,7 @@ func (c *FSContext) OpenFile(fs sys.FS, path string, flag sys.Oflag, perm fs.Fil
 	if f, errno := fs.OpenFile(path, flag, perm); errno != 0 {
 		return 0, errno
 	} else {
-		fe := &FileEntry{FS: fs, File: f}
+		fe := &FileEntry{FS: fs, File: fsapi.Adapt(f)}
 		if path == "/" || path == "." {
 			fe.Name = ""
 		} else {
@@ -339,18 +340,20 @@ func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, sys.Errno) {
 		return 0, sys.EBADF // Not a sock
 	}
 
-	var conn socketapi.TCPConn
-	var errno sys.Errno
-	if conn, errno = sock.Accept(); errno != 0 {
+	conn, errno := sock.Accept()
+	if errno != 0 {
 		return 0, errno
-	} else if nonblock {
-		if errno = conn.SetNonblock(true); errno != 0 {
+	}
+
+	fe := &FileEntry{File: fsapi.Adapt(conn)}
+
+	if nonblock {
+		if errno = fe.File.SetNonblock(true); errno != 0 {
 			_ = conn.Close()
 			return 0, errno
 		}
 	}
 
-	fe := &FileEntry{File: conn}
 	if newFD, ok := c.openedFiles.Insert(fe); !ok {
 		return 0, sys.EBADF
 	} else {
@@ -426,7 +429,7 @@ func (c *Context) InitFSContext(
 	}
 
 	for _, tl := range tcpListeners {
-		c.fsc.openedFiles.Insert(&FileEntry{IsPreopen: true, File: sysfs.NewTCPListenerFile(tl)})
+		c.fsc.openedFiles.Insert(&FileEntry{IsPreopen: true, File: fsapi.Adapt(sysfs.NewTCPListenerFile(tl))})
 	}
 	return nil
 }
diff --git a/internal/sys/lazy.go b/internal/sys/lazy.go
index 0deb79c5..fe233d29 100644
--- a/internal/sys/lazy.go
+++ b/internal/sys/lazy.go
@@ -2,6 +2,7 @@ package sys
 
 import (
 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/sys"
 )
 
@@ -16,8 +17,8 @@ type lazyDir struct {
 }
 
 // Dev implements the same method as documented on sys.File
-func (r *lazyDir) Dev() (uint64, experimentalsys.Errno) {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Dev() (uint64, experimentalsys.Errno) {
+	if f, ok := d.file(); !ok {
 		return 0, experimentalsys.EBADF
 	} else {
 		return f.Dev()
@@ -25,8 +26,8 @@ func (r *lazyDir) Dev() (uint64, experimentalsys.Errno) {
 }
 
 // Ino implements the same method as documented on sys.File
-func (r *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) {
+	if f, ok := d.file(); !ok {
 		return 0, experimentalsys.EBADF
 	} else {
 		return f.Ino()
@@ -34,10 +35,10 @@ func (r *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) {
 }
 
 // IsDir implements the same method as documented on sys.File
-func (r *lazyDir) IsDir() (bool, experimentalsys.Errno) {
+func (d *lazyDir) IsDir() (bool, experimentalsys.Errno) {
 	// Note: we don't return a constant because we don't know if this is really
 	// backed by a dir, until the first call.
-	if f, ok := r.file(); !ok {
+	if f, ok := d.file(); !ok {
 		return false, experimentalsys.EBADF
 	} else {
 		return f.IsDir()
@@ -45,18 +46,18 @@ func (r *lazyDir) IsDir() (bool, experimentalsys.Errno) {
 }
 
 // IsAppend implements the same method as documented on sys.File
-func (r *lazyDir) IsAppend() bool {
+func (d *lazyDir) IsAppend() bool {
 	return false
 }
 
 // SetAppend implements the same method as documented on sys.File
-func (r *lazyDir) SetAppend(bool) experimentalsys.Errno {
+func (d *lazyDir) SetAppend(bool) experimentalsys.Errno {
 	return experimentalsys.EISDIR
 }
 
 // Seek implements the same method as documented on sys.File
-func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) {
+	if f, ok := d.file(); !ok {
 		return 0, experimentalsys.EBADF
 	} else {
 		return f.Seek(offset, whence)
@@ -64,8 +65,8 @@ func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experim
 }
 
 // Stat implements the same method as documented on sys.File
-func (r *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) {
+	if f, ok := d.file(); !ok {
 		return sys.Stat_t{}, experimentalsys.EBADF
 	} else {
 		return f.Stat()
@@ -73,8 +74,8 @@ func (r *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) {
 }
 
 // Readdir implements the same method as documented on sys.File
-func (r *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) {
+	if f, ok := d.file(); !ok {
 		return nil, experimentalsys.EBADF
 	} else {
 		return f.Readdir(n)
@@ -82,8 +83,8 @@ func (r *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experi
 }
 
 // Sync implements the same method as documented on sys.File
-func (r *lazyDir) Sync() experimentalsys.Errno {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Sync() experimentalsys.Errno {
+	if f, ok := d.file(); !ok {
 		return experimentalsys.EBADF
 	} else {
 		return f.Sync()
@@ -91,8 +92,8 @@ func (r *lazyDir) Sync() experimentalsys.Errno {
 }
 
 // Datasync implements the same method as documented on sys.File
-func (r *lazyDir) Datasync() experimentalsys.Errno {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Datasync() experimentalsys.Errno {
+	if f, ok := d.file(); !ok {
 		return experimentalsys.EBADF
 	} else {
 		return f.Datasync()
@@ -100,8 +101,8 @@ func (r *lazyDir) Datasync() experimentalsys.Errno {
 }
 
 // Utimens implements the same method as documented on sys.File
-func (r *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno {
-	if f, ok := r.file(); !ok {
+func (d *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno {
+	if f, ok := d.file(); !ok {
 		return experimentalsys.EBADF
 	} else {
 		return f.Utimens(atim, mtim)
@@ -109,15 +110,15 @@ func (r *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno {
 }
 
 // file returns the underlying file or false if it doesn't exist.
-func (r *lazyDir) file() (experimentalsys.File, bool) {
-	if f := r.f; r.f != nil {
+func (d *lazyDir) file() (experimentalsys.File, bool) {
+	if f := d.f; d.f != nil {
 		return f, true
 	}
 	var errno experimentalsys.Errno
-	r.f, errno = r.fs.OpenFile(".", experimentalsys.O_RDONLY, 0)
+	d.f, errno = d.fs.OpenFile(".", experimentalsys.O_RDONLY, 0)
 	switch errno {
 	case 0:
-		return r.f, true
+		return d.f, true
 	case experimentalsys.ENOENT:
 		return nil, false
 	default:
@@ -126,10 +127,25 @@ func (r *lazyDir) file() (experimentalsys.File, bool) {
 }
 
 // Close implements fs.File
-func (r *lazyDir) Close() experimentalsys.Errno {
-	f := r.f
+func (d *lazyDir) Close() experimentalsys.Errno {
+	f := d.f
 	if f == nil {
 		return 0 // never opened
 	}
 	return f.Close()
 }
+
+// IsNonblock implements the same method as documented on fsapi.File
+func (d *lazyDir) IsNonblock() bool {
+	return false
+}
+
+// SetNonblock implements the same method as documented on fsapi.File
+func (d *lazyDir) SetNonblock(bool) experimentalsys.Errno {
+	return experimentalsys.EISDIR
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (d *lazyDir) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) {
+	return false, experimentalsys.ENOSYS
+}
diff --git a/internal/sys/stdio.go b/internal/sys/stdio.go
index 6a249c38..32c33661 100644
--- a/internal/sys/stdio.go
+++ b/internal/sys/stdio.go
@@ -5,6 +5,7 @@ import (
 	"os"
 
 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/internal/sysfs"
 	"github.com/tetratelabs/wazero/sys"
 )
@@ -47,9 +48,9 @@ func (noopStdinFile) Read([]byte) (int, experimentalsys.Errno) {
 	return 0, 0 // Always EOF
 }
 
-// Poll implements the same method as documented on sys.File
-func (noopStdinFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
-	if flag != experimentalsys.POLLIN {
+// Poll implements the same method as documented on fsapi.File
+func (noopStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
+	if flag != fsapi.POLLIN {
 		return false, experimentalsys.ENOTSUP
 	}
 	return true, 0 // always ready to read nothing
@@ -83,6 +84,21 @@ func (noopStdioFile) IsDir() (bool, experimentalsys.Errno) {
 // Close implements the same method as documented on sys.File
 func (noopStdioFile) Close() (errno experimentalsys.Errno) { return }
 
+// IsNonblock implements the same method as documented on fsapi.File
+func (noopStdioFile) IsNonblock() bool {
+	return false
+}
+
+// SetNonblock implements the same method as documented on fsapi.File
+func (noopStdioFile) SetNonblock(bool) experimentalsys.Errno {
+	return experimentalsys.ENOSYS
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (noopStdioFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) {
+	return false, experimentalsys.ENOSYS
+}
+
 func stdinFileEntry(r io.Reader) (*FileEntry, error) {
 	if r == nil {
 		return &FileEntry{Name: "stdin", IsPreopen: true, File: &noopStdinFile{}}, nil
diff --git a/internal/sysfs/file.go b/internal/sysfs/file.go
index a2258c3f..9a77205b 100644
--- a/internal/sysfs/file.go
+++ b/internal/sysfs/file.go
@@ -7,10 +7,11 @@ import (
 	"time"
 
 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/sys"
 )
 
-func NewStdioFile(stdin bool, f fs.File) (experimentalsys.File, error) {
+func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) {
 	// Return constant stat, which has fake times, but keep the underlying
 	// file mode. Fake times are needed to pass wasi-testsuite.
 	// https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19
@@ -26,7 +27,7 @@ func NewStdioFile(stdin bool, f fs.File) (experimentalsys.File, error) {
 	} else {
 		flag = experimentalsys.O_WRONLY
 	}
-	var file experimentalsys.File
+	var file fsapi.File
 	if of, ok := f.(*os.File); ok {
 		// This is ok because functions that need path aren't used by stdioFile
 		file = newOsFile("", flag, 0, of)
@@ -65,7 +66,7 @@ func OpenFSFile(fs fs.FS, path string, flag experimentalsys.Oflag, perm fs.FileM
 }
 
 type stdioFile struct {
-	experimentalsys.File
+	fsapi.File
 	st sys.Stat_t
 }
 
@@ -343,6 +344,21 @@ func (f *fsFile) close() experimentalsys.Errno {
 	return experimentalsys.UnwrapOSError(f.file.Close())
 }
 
+// IsNonblock implements the same method as documented on fsapi.File
+func (f *fsFile) IsNonblock() bool {
+	return false
+}
+
+// SetNonblock implements the same method as documented on fsapi.File
+func (f *fsFile) SetNonblock(bool) experimentalsys.Errno {
+	return experimentalsys.ENOSYS
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (f *fsFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) {
+	return false, experimentalsys.ENOSYS
+}
+
 // dirError is used for commands that work against a directory, but not a file.
 func dirError(f experimentalsys.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno {
 	if vErrno := validate(f, isClosed, false, true); vErrno != 0 {
diff --git a/internal/sysfs/osfile.go b/internal/sysfs/osfile.go
index adb664fc..ac0df777 100644
--- a/internal/sysfs/osfile.go
+++ b/internal/sysfs/osfile.go
@@ -7,10 +7,11 @@ import (
 	"runtime"
 
 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/sys"
 )
 
-func newOsFile(path string, flag experimentalsys.Oflag, perm fs.FileMode, f *os.File) experimentalsys.File {
+func newOsFile(path string, flag experimentalsys.Oflag, perm fs.FileMode, f *os.File) fsapi.File {
 	// Windows cannot read files written to a directory after it was opened.
 	// This was noticed in #1087 in zig tests. Use a flag instead of a
 	// different type.
@@ -102,12 +103,12 @@ func (f *osFile) reopen() (errno experimentalsys.Errno) {
 	return
 }
 
-// IsNonblock implements the same method as documented on sys.File
+// IsNonblock implements the same method as documented on fsapi.File
 func (f *osFile) IsNonblock() bool {
 	return isNonblock(f)
 }
 
-// SetNonblock implements the same method as documented on sys.File
+// SetNonblock implements the same method as documented on fsapi.File
 func (f *osFile) SetNonblock(enable bool) (errno experimentalsys.Errno) {
 	if enable {
 		f.flag |= experimentalsys.O_NONBLOCK
@@ -178,8 +179,8 @@ func (f *osFile) Seek(offset int64, whence int) (newOffset int64, errno experime
 	return
 }
 
-// Poll implements the same method as documented on sys.File
-func (f *osFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
+// Poll implements the same method as documented on fsapi.File
+func (f *osFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
 	return poll(f.fd, flag, timeoutMillis)
 }
 
diff --git a/internal/sysfs/poll.go b/internal/sysfs/poll.go
index 4041699f..f5c98295 100644
--- a/internal/sysfs/poll.go
+++ b/internal/sysfs/poll.go
@@ -4,11 +4,12 @@ package sysfs
 
 import (
 	"github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 )
 
 // poll implements `Poll` as documented on sys.File via a file descriptor.
-func poll(fd uintptr, flag sys.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) {
-	if flag != sys.POLLIN {
+func poll(fd uintptr, flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) {
+	if flag != fsapi.POLLIN {
 		return false, sys.ENOTSUP
 	}
 	fds := []pollFd{newPollFd(fd, _POLLIN, 0)}
diff --git a/internal/sysfs/poll_unsupported.go b/internal/sysfs/poll_unsupported.go
index f546e154..ebe8a6fa 100644
--- a/internal/sysfs/poll_unsupported.go
+++ b/internal/sysfs/poll_unsupported.go
@@ -4,9 +4,10 @@ package sysfs
 
 import (
 	"github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 )
 
-// poll implements `Poll` as documented on sys.File via a file descriptor.
-func poll(fd uintptr, flag sys.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) {
+// poll implements `Poll` as documented on fsapi.File via a file descriptor.
+func poll(uintptr, fsapi.Pflag, int32) (bool, sys.Errno) {
 	return false, sys.ENOSYS
 }
diff --git a/internal/sysfs/sock_unix.go b/internal/sysfs/sock_unix.go
index 86db1ef0..3698f560 100644
--- a/internal/sysfs/sock_unix.go
+++ b/internal/sysfs/sock_unix.go
@@ -7,6 +7,7 @@ import (
 	"syscall"
 
 	"github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	socketapi "github.com/tetratelabs/wazero/internal/sock"
 )
 
@@ -56,24 +57,30 @@ func (f *tcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) {
 	return &tcpConnFile{fd: uintptr(nfd)}, 0
 }
 
-// SetNonblock implements the same method as documented on sys.File
+// Close implements the same method as documented on sys.File
+func (f *tcpListenerFile) Close() sys.Errno {
+	return sys.UnwrapOSError(syscall.Close(int(f.fd)))
+}
+
+// Addr is exposed for testing.
+func (f *tcpListenerFile) Addr() *net.TCPAddr {
+	return f.addr
+}
+
+// SetNonblock implements the same method as documented on fsapi.File
 func (f *tcpListenerFile) SetNonblock(enabled bool) sys.Errno {
 	f.nonblock = enabled
 	return sys.UnwrapOSError(setNonblock(f.fd, enabled))
 }
 
+// IsNonblock implements the same method as documented on fsapi.File
 func (f *tcpListenerFile) IsNonblock() bool {
 	return f.nonblock
 }
 
-// Close implements the same method as documented on sys.File
-func (f *tcpListenerFile) Close() sys.Errno {
-	return sys.UnwrapOSError(syscall.Close(int(f.fd)))
-}
-
-// Addr is exposed for testing.
-func (f *tcpListenerFile) Addr() *net.TCPAddr {
-	return f.addr
+// Poll implements the same method as documented on fsapi.File
+func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) {
+	return false, sys.ENOSYS
 }
 
 var _ socketapi.TCPConn = (*tcpConnFile)(nil)
@@ -96,17 +103,6 @@ func newTcpConn(tc *net.TCPConn) socketapi.TCPConn {
 	return &tcpConnFile{fd: f.Fd()}
 }
 
-// SetNonblock implements the same method as documented on sys.File
-func (f *tcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) {
-	f.nonblock = enabled
-	return sys.UnwrapOSError(setNonblock(f.fd, enabled))
-}
-
-// IsNonblock implements the same method as documented on sys.File
-func (f *tcpConnFile) IsNonblock() bool {
-	return f.nonblock
-}
-
 // Read implements the same method as documented on sys.File
 func (f *tcpConnFile) Read(buf []byte) (n int, errno sys.Errno) {
 	n, err := syscall.Read(int(f.fd), buf)
@@ -166,3 +162,19 @@ func (f *tcpConnFile) close() sys.Errno {
 	f.closed = true
 	return sys.UnwrapOSError(syscall.Shutdown(int(f.fd), syscall.SHUT_RDWR))
 }
+
+// SetNonblock implements the same method as documented on fsapi.File
+func (f *tcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) {
+	f.nonblock = enabled
+	return sys.UnwrapOSError(setNonblock(f.fd, enabled))
+}
+
+// IsNonblock implements the same method as documented on fsapi.File
+func (f *tcpConnFile) IsNonblock() bool {
+	return f.nonblock
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) {
+	return false, sys.ENOSYS
+}
diff --git a/internal/sysfs/sock_windows.go b/internal/sysfs/sock_windows.go
index c289559a..ed275e62 100644
--- a/internal/sysfs/sock_windows.go
+++ b/internal/sysfs/sock_windows.go
@@ -8,6 +8,7 @@ import (
 	"unsafe"
 
 	"github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	socketapi "github.com/tetratelabs/wazero/internal/sock"
 )
 
@@ -17,8 +18,6 @@ const (
 	MSG_PEEK = 0x2
 	// _FIONBIO is the flag to set the O_NONBLOCK flag on socket handles using ioctlsocket.
 	_FIONBIO = 0x8004667e
-	// _WASWOULDBLOCK corresponds to syscall.EWOULDBLOCK in WinSock.
-	_WASWOULDBLOCK = 10035
 )
 
 var (
@@ -26,12 +25,8 @@ var (
 	modws2_32 = syscall.NewLazyDLL("ws2_32.dll")
 	// procrecvfrom exposes recvfrom from WinSock.
 	procrecvfrom = modws2_32.NewProc("recvfrom")
-	// procaccept exposes accept from WinSock.
-	procaccept = modws2_32.NewProc("accept")
 	// procioctlsocket exposes ioctlsocket from WinSock.
 	procioctlsocket = modws2_32.NewProc("ioctlsocket")
-	// procselect exposes select from WinSock.
-	procselect = modws2_32.NewProc("select")
 )
 
 // recvfrom exposes the underlying syscall in Windows.
@@ -90,8 +85,8 @@ func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, sys.Errno))
 	return
 }
 
-func _pollSock(conn syscall.Conn, flag sys.Pflag, timeoutMillis int32) (bool, sys.Errno) {
-	if flag != sys.POLLIN {
+func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) {
+	if flag != fsapi.POLLIN {
 		return false, sys.ENOTSUP
 	}
 	n, errno := syscallConnControl(conn, func(fd uintptr) (int, sys.Errno) {
@@ -126,7 +121,7 @@ type winTcpListenerFile struct {
 func (f *winTcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) {
 	// Ensure we have an incoming connection using winsock_select, otherwise return immediately.
 	if f.nonblock {
-		if ready, errno := _pollSock(f.tl, sys.POLLIN, 0); !ready || errno != 0 {
+		if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 {
 			return nil, sys.EAGAIN
 		}
 	}
@@ -141,12 +136,25 @@ func (f *winTcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) {
 	}
 }
 
-// IsNonblock implements File.IsNonblock
+// Close implements the same method as documented on sys.File
+func (f *winTcpListenerFile) Close() sys.Errno {
+	if !f.closed {
+		return sys.UnwrapOSError(f.tl.Close())
+	}
+	return 0
+}
+
+// Addr is exposed for testing.
+func (f *winTcpListenerFile) Addr() *net.TCPAddr {
+	return f.tl.Addr().(*net.TCPAddr)
+}
+
+// IsNonblock implements the same method as documented on fsapi.File
 func (f *winTcpListenerFile) IsNonblock() bool {
 	return f.nonblock
 }
 
-// SetNonblock implements the same method as documented on sys.File
+// SetNonblock implements the same method as documented on fsapi.File
 func (f *winTcpListenerFile) SetNonblock(enabled bool) sys.Errno {
 	f.nonblock = enabled
 	_, errno := syscallConnControl(f.tl, func(fd uintptr) (int, sys.Errno) {
@@ -155,17 +163,9 @@ func (f *winTcpListenerFile) SetNonblock(enabled bool) sys.Errno {
 	return errno
 }
 
-// Close implements the same method as documented on sys.File
-func (f *winTcpListenerFile) Close() sys.Errno {
-	if !f.closed {
-		return sys.UnwrapOSError(f.tl.Close())
-	}
-	return 0
-}
-
-// Addr is exposed for testing.
-func (f *winTcpListenerFile) Addr() *net.TCPAddr {
-	return f.tl.Addr().(*net.TCPAddr)
+// Poll implements the same method as documented on fsapi.File
+func (f *winTcpListenerFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) {
+	return false, sys.ENOSYS
 }
 
 var _ socketapi.TCPConn = (*winTcpConnFile)(nil)
@@ -189,20 +189,6 @@ func newTcpConn(tc *net.TCPConn) socketapi.TCPConn {
 	return &winTcpConnFile{tc: tc}
 }
 
-// SetNonblock implements the same method as documented on sys.File
-func (f *winTcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) {
-	f.nonblock = true
-	_, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) {
-		return 0, sys.UnwrapOSError(setNonblockSocket(syscall.Handle(fd), enabled))
-	})
-	return
-}
-
-// IsNonblock implements File.IsNonblock
-func (f *winTcpConnFile) IsNonblock() bool {
-	return f.nonblock
-}
-
 // Read implements the same method as documented on sys.File
 func (f *winTcpConnFile) Read(buf []byte) (n int, errno sys.Errno) {
 	if len(buf) == 0 {
@@ -278,3 +264,22 @@ func (f *winTcpConnFile) close() sys.Errno {
 	f.closed = true
 	return f.Shutdown(syscall.SHUT_RDWR)
 }
+
+// IsNonblock implements the same method as documented on fsapi.File
+func (f *winTcpConnFile) IsNonblock() bool {
+	return f.nonblock
+}
+
+// SetNonblock implements the same method as documented on fsapi.File
+func (f *winTcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) {
+	f.nonblock = true
+	_, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) {
+		return 0, sys.UnwrapOSError(setNonblockSocket(syscall.Handle(fd), enabled))
+	})
+	return
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (f *winTcpConnFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) {
+	return false, sys.ENOSYS
+}


Test patch:
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index e8883b21..76ad32bc 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/internal/fstest"
 	"github.com/tetratelabs/wazero/internal/platform"
 	"github.com/tetratelabs/wazero/internal/sys"
@@ -247,7 +248,7 @@ func Test_fdFdstatGet(t *testing.T) {
 	}}}).OpenFile("stdin", 0, 0)
 	require.EqualErrno(t, 0, errno)
 
-	stdin.File = stdinFile
+	stdin.File = fsapi.Adapt(stdinFile)
 
 	// Make this file writeable, to ensure flags read-back correctly.
 	fileFD, errno := fsc.OpenFile(preopen, file, experimentalsys.O_RDWR, 0)
diff --git a/imports/wasi_snapshot_preview1/poll_test.go b/imports/wasi_snapshot_preview1/poll_test.go
index e077ec65..a5357e79 100644
--- a/imports/wasi_snapshot_preview1/poll_test.go
+++ b/imports/wasi_snapshot_preview1/poll_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/wasip1"
@@ -161,7 +162,7 @@ func Test_pollOneoff_Stdin(t *testing.T) {
 		name                                   string
 		in, out, nsubscriptions, resultNevents uint32
 		mem                                    []byte // at offset in
-		stdin                                  experimentalsys.File
+		stdin                                  fsapi.File
 		expectedErrno                          wasip1.Errno
 		expectedMem                            []byte // at offset out
 		expectedLog                            string
@@ -442,7 +443,7 @@ func Test_pollOneoff_Stdin(t *testing.T) {
 	}
 }
 
-func setStdin(t *testing.T, mod api.Module, stdin experimentalsys.File) {
+func setStdin(t *testing.T, mod api.Module, stdin fsapi.File) {
 	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
 	f, ok := fsc.LookupFile(sys.FdStdin)
 	require.True(t, ok)
@@ -613,8 +614,8 @@ type neverReadyTtyStdinFile struct {
 }
 
 // Poll implements the same method as documented on sys.File
-func (neverReadyTtyStdinFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
-	if flag != experimentalsys.POLLIN {
+func (neverReadyTtyStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
+	if flag != fsapi.POLLIN {
 		return false, experimentalsys.ENOTSUP
 	}
 	switch {
@@ -632,8 +633,8 @@ type pollStdinFile struct {
 }
 
 // Poll implements the same method as documented on sys.File
-func (p *pollStdinFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
-	if flag != experimentalsys.POLLIN {
+func (p *pollStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
+	if flag != fsapi.POLLIN {
 		return false, experimentalsys.ENOTSUP
 	}
 	return p.ready, 0
diff --git a/imports/wasi_snapshot_preview1/wasi_stdlib_test.go b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go
index 3e4933ff..93ac1a56 100644
--- a/imports/wasi_snapshot_preview1/wasi_stdlib_test.go
+++ b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go
@@ -20,8 +20,8 @@ import (
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
 	experimentalsock "github.com/tetratelabs/wazero/experimental/sock"
-	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
 	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/internal/fstest"
 	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
@@ -329,7 +329,7 @@ func Test_Poll(t *testing.T) {
 	tests := []struct {
 		name            string
 		args            []string
-		stdin           experimentalsys.File
+		stdin           fsapi.File
 		expectedOutput  string
 		expectedTimeout time.Duration
 	}{
diff --git a/internal/sysfs/file_test.go b/internal/sysfs/file_test.go
index 00501154..1b97eb86 100644
--- a/internal/sysfs/file_test.go
+++ b/internal/sysfs/file_test.go
@@ -11,6 +11,7 @@ import (
 	gofstest "testing/fstest"
 
 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/internal/platform"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/sys"
@@ -319,7 +320,7 @@ func TestFileReadAndPread(t *testing.T) {
 }
 
 func TestFilePoll_POLLIN(t *testing.T) {
-	pflag := experimentalsys.POLLIN
+	pflag := fsapi.POLLIN
 
 	// Test using os.Pipe as it is known to support poll.
 	r, w, err := os.Pipe()
@@ -355,7 +356,7 @@ func TestFilePoll_POLLIN(t *testing.T) {
 }
 
 func TestFilePoll_POLLOUT(t *testing.T) {
-	pflag := experimentalsys.POLLOUT
+	pflag := fsapi.POLLOUT
 
 	// Test using os.Pipe as it is known to support poll.
 	r, w, err := os.Pipe()
diff --git a/internal/sysfs/sock_test.go b/internal/sysfs/sock_test.go
index bdcb721e..82b8ab56 100644
--- a/internal/sysfs/sock_test.go
+++ b/internal/sysfs/sock_test.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/tetratelabs/wazero/experimental/sys"
+	"github.com/tetratelabs/wazero/internal/fsapi"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
@@ -117,15 +118,16 @@ func TestTcpConnFile_SetNonblock(t *testing.T) {
 	require.NoError(t, err)
 	defer tcp.Close() //nolint
 
-	errno := lf.SetNonblock(true)
+	nblf := fsapi.Adapt(lf)
+	errno := nblf.SetNonblock(true)
 	require.EqualErrno(t, 0, errno)
-	require.True(t, lf.IsNonblock())
+	require.True(t, nblf.IsNonblock())
 
 	conn, errno := lf.Accept()
 	require.EqualErrno(t, 0, errno)
 	defer conn.Close()
 
-	file := newTcpConn(tcp)
+	file := fsapi.Adapt(newTcpConn(tcp))
 	errno = file.SetNonblock(true)
 	require.EqualErrno(t, 0, errno)
 	require.True(t, file.IsNonblock())