vendor: github.com/tetratelabs/wazero v1.11.0

- Streamline build tags: remove tinygo, cgo
- Fix race condition in refCount initialization
- Simplify utimens. Use `syscall.UtimesNano` to avoid a macOS `go:linkname`.
- Change version policy to two versions.
- Update Wasm 2.0 spec tests.
- Use golang.org/x/sys

full diff: https://github.com/tetratelabs/wazero/compare/v1.10.1...v1.11.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-12-22 15:06:09 +01:00
parent fba74ac758
commit 9176746aba
90 changed files with 366 additions and 884 deletions

2
go.mod
View File

@@ -217,7 +217,7 @@ require (
github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/spdx/tools-golang v0.5.5 // indirect github.com/spdx/tools-golang v0.5.5 // indirect
github.com/stretchr/testify v1.11.1 // indirect github.com/stretchr/testify v1.11.1 // indirect
github.com/tetratelabs/wazero v1.10.1 // indirect github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/tinylib/msgp v1.3.0 // indirect github.com/tinylib/msgp v1.3.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect

4
go.sum
View File

@@ -602,8 +602,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26 h1:mWCRvpoEMVlslxEvvptKgIUb35va9yj9Oq5wGw/er5I= github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26 h1:mWCRvpoEMVlslxEvvptKgIUb35va9yj9Oq5wGw/er5I=
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26/go.mod h1:0uD3VMXkZ7Bw0ojGCwDzebBBzPBXtzEZeXai+56BLX4= github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26/go.mod h1:0uD3VMXkZ7Bw0ojGCwDzebBBzPBXtzEZeXai+56BLX4=
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4= github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=

View File

@@ -108,28 +108,29 @@ internal/testing/dwarftestdata/testdata/rust/main.wasm.xz:
@mv $(@D)/target/$(cargo_target)/release/$(@F) $(@D) @mv $(@D)/target/$(cargo_target)/release/$(@F) $(@D)
spectest_base_dir := internal/integration_test/spectest spectest_base_dir := internal/integration_test/spectest
spectest_v1_dir := $(spectest_base_dir)/v1 spectest_v1_dir := $(spectest_base_dir)/v1
spectest_v1_testdata_dir := $(spectest_v1_dir)/testdata spectest_v1_testdata_dir := $(spectest_v1_dir)/testdata
spec_version_v1 := wg-1.0 spec_version_v1 := wg-1.0
spectest_v2_dir := $(spectest_base_dir)/v2 spectest_v2_dir := $(spectest_base_dir)/v2
spectest_v2_testdata_dir := $(spectest_v2_dir)/testdata spectest_v2_testdata_dir := $(spectest_v2_dir)/testdata
spec_version_v2 := wg-2.0
# Latest draft state as of March 12, 2024.
spec_version_v2 := 1c5e5d178bd75c79b7a12881c529098beaee2a05
spectest_threads_dir := $(spectest_base_dir)/threads spectest_threads_dir := $(spectest_base_dir)/threads
spectest_threads_testdata_dir := $(spectest_threads_dir)/testdata spectest_threads_testdata_dir := $(spectest_threads_dir)/testdata
# From https://github.com/WebAssembly/threads/tree/upstream-rebuild which has not been merged to main yet. spec_version_threads := ff17701446d8e2086142423ef77ae947a025e26f
# It will likely be renamed to main in the future - https://github.com/WebAssembly/threads/issues/216.
spec_version_threads := 3635ca51a17e57e106988846c5b0e0cc48ac04fc
spectest_tail_call_dir := $(spectest_base_dir)/tail-call spectest_tail_call_dir := $(spectest_base_dir)/tail-call
spectest_tail_call_testdata_dir := $(spectest_tail_call_dir)/testdata spectest_tail_call_testdata_dir := $(spectest_tail_call_dir)/testdata
spec_version_tail_call := 4fd2339b5e9709e74b326797f69a88b13eac4d47 spec_version_tail_call := 88e97b0f742f4c3ee01fea683da130f344dd7b02
.PHONY: build.spectest .PHONY: build.spectest
build.spectest: build.spectest:
@$(MAKE) build.spectest.v1 @$(MAKE) build.spectest.v1
@$(MAKE) build.spectest.v2 @$(MAKE) build.spectest.v2
@$(MAKE) build.spectest.threads
@$(MAKE) build.spectest.tail_call
.PHONY: build.spectest.v1 .PHONY: build.spectest.v1
build.spectest.v1: # Note: wabt by default uses >1.0 features, so wast2json flags might drift as they include more. See WebAssembly/wabt#1878 build.spectest.v1: # Note: wabt by default uses >1.0 features, so wast2json flags might drift as they include more. See WebAssembly/wabt#1878
@@ -160,6 +161,7 @@ build.spectest.v1: # Note: wabt by default uses >1.0 features, so wast2json flag
.PHONY: build.spectest.v2 .PHONY: build.spectest.v2
build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory. build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory.
@rm -rf $(spectest_v2_testdata_dir)
@mkdir -p $(spectest_v2_testdata_dir) @mkdir -p $(spectest_v2_testdata_dir)
@cd $(spectest_v2_testdata_dir) \ @cd $(spectest_v2_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core?ref=$(spec_version_v2)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O && curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core?ref=$(spec_version_v2)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O
@@ -173,19 +175,21 @@ build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory.
# https://github.com/WebAssembly/wabt/issues/2348#issuecomment-1878003959 # https://github.com/WebAssembly/wabt/issues/2348#issuecomment-1878003959
.PHONY: build.spectest.threads .PHONY: build.spectest.threads
build.spectest.threads: build.spectest.threads:
@rm -rf $(spectest_threads_testdata_dir)
@mkdir -p $(spectest_threads_testdata_dir) @mkdir -p $(spectest_threads_testdata_dir)
@cd $(spectest_threads_testdata_dir) \ @cd $(spectest_threads_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/threads/contents/test/core?ref=$(spec_version_threads)' | jq -r '.[]| .download_url' | grep -E "atomic.wast" | xargs -Iurl curl -sJL url -O && curl -sSL 'https://api.github.com/repos/WebAssembly/threads/contents/test/core/threads?ref=$(spec_version_threads)' | jq -r '.[]| .download_url' | grep -E "/atomic.wast" | xargs -Iurl curl -sJL url -O
@cd $(spectest_threads_testdata_dir) && for f in `find . -name '*.wast'`; do \ @cd $(spectest_threads_testdata_dir) && for f in `find . -name '*.wast'`; do \
wast2json --enable-threads --debug-names $$f; \ wast2json --enable-threads --debug-names $$f; \
done done
.PHONY: build.spectest.tail_call .PHONY: build.spectest.tail_call
build.spectest.tail_call: build.spectest.tail_call:
mkdir -p $(spectest_tail_call_testdata_dir) @rm -rf $(spectest_tail_call_testdata_dir)
cd $(spectest_tail_call_testdata_dir) \ @mkdir -p $(spectest_tail_call_testdata_dir)
@cd $(spectest_tail_call_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/testsuite/contents/proposals/tail-call?ref=$(spec_version_tail_call)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O && curl -sSL 'https://api.github.com/repos/WebAssembly/testsuite/contents/proposals/tail-call?ref=$(spec_version_tail_call)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O
cd $(spectest_tail_call_testdata_dir) && for f in `find . -name '*.wast'`; do \ @cd $(spectest_tail_call_testdata_dir) && for f in `find . -name '*.wast'`; do \
wast2json --enable-tail-call --debug-names $$f; \ wast2json --enable-tail-call --debug-names $$f; \
done done

View File

@@ -7,24 +7,19 @@ have heavy impact usually due to CGO. By avoiding CGO, wazero avoids
prerequisites such as shared libraries or libc, and lets users keep features prerequisites such as shared libraries or libc, and lets users keep features
like cross compilation. like cross compilation.
Avoiding go.mod dependencies reduces interference on Go version support, and Avoiding most `go.mod` dependencies reduces interference on Go version support,
size of a statically compiled binary. However, doing so brings some and size of a statically compiled binary. However, doing so brings some
responsibility into the project. responsibility into the project.
Go's native platform support is good: We don't need platform-specific code to Go's native platform support is good: we don't need platform-specific code to
get monotonic time, nor do we need much work to implement certain features get monotonic time, nor do we need much work to implement certain features
needed by our compiler such as `mmap`. That said, Go does not support all needed by our compiler such as `mmap`. That said, Go does not support all
common operating systems to the same degree. For example, Go 1.18 includes common operating systems to the same degree.
`Mprotect` on Linux and Darwin, but not FreeBSD.
The general tradeoff the project takes from a zero dependency policy is more The general tradeoff the project takes from a strict dependency policy is more
explicit support of platforms (in the compiler runtime), as well a larger and explicit support of platforms (in the compiler runtime), as well a larger and
more technically difficult codebase. more technically difficult codebase.
At some point, we may allow extensions to supply their own platform-specific
hooks. Until then, one end user impact/tradeoff is some glitches trying
untested platforms (with the Compiler runtime).
### Why do we use CGO to implement system calls on darwin? ### Why do we use CGO to implement system calls on darwin?
wazero is dependency and CGO free by design. In some cases, we have code that wazero is dependency and CGO free by design. In some cases, we have code that
@@ -45,25 +40,20 @@ This plays to our advantage for system calls that aren't yet exposed in the Go
standard library, notably `futimens` for nanosecond-precision timestamp standard library, notably `futimens` for nanosecond-precision timestamp
manipulation. manipulation.
### Why not x/sys ### Why x/sys
Going beyond Go's SDK limitations can be accomplished with their [x/sys library](https://pkg.go.dev/golang.org/x/sys/unix). The [x/sys library](https://pkg.go.dev/golang.org/x/sys/unix) is currently
For example, this includes `zsyscall_freebsd_amd64.go` missing from the Go SDK. our only `go.mod` dependency.
However, like all dependencies, x/sys is a source of conflict. For example, That module is maintained by the Go authors, and covers OSes that the syscall
x/sys had to be in order to upgrade to Go 1.18. package neglects.
If we depended on x/sys, we could get more precise functionality needed for After [heavy consideration](https://github.com/wazero/wazero/issues/2434) we
features such as clocks or more platform support for the compiler runtime. decided to it as a dependency.
That said, formally supporting an operating system may still require testing as Using was shown to improve the experience of using wazero on older,
even use of x/sys can require platform-specifics. For example, [mmap-go](https://github.com/edsrzf/mmap-go) or less common, OSes without increasing the maintenance work, or creating
uses x/sys, but also mentions limitations, some not surmountable with x/sys deployment issues for users of wazero.
alone.
Regardless, we may at some point introduce a separate go.mod for users to use
x/sys as a platform plugin without forcing all users to maintain that
dependency.
## Project structure ## Project structure
@@ -756,7 +746,7 @@ value (possibly `PWD`). Those unable to control the compiled code should only
use absolute paths in configuration. use absolute paths in configuration.
See See
* https://github.com/golang/go/blob/go1.20/src/syscall/fs_js.go#L324 * https://github.com/golang/go/blob/go1.24.0/src/syscall/fs_js.go#L341
* https://github.com/WebAssembly/wasi-libc/pull/214#issue-673090117 * https://github.com/WebAssembly/wasi-libc/pull/214#issue-673090117
* https://github.com/ziglang/zig/blob/53a9ee699a35a3d245ab6d1dac1f0687a4dcb42c/src/main.zig#L32 * https://github.com/ziglang/zig/blob/53a9ee699a35a3d245ab6d1dac1f0687a4dcb42c/src/main.zig#L32
@@ -1064,7 +1054,7 @@ reason, wazero adds overhead to synthesize dot entries despite it being
unnecessary for most users. unnecessary for most users.
See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
See https://github.com/golang/go/blob/go1.20/src/os/dir_unix.go#L108-L110 See https://github.com/golang/go/blob/go1.24.0/src/os/dir_unix.go#L122-L124
See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041 See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041
### Why don't we pre-populate an inode for the dot-dot ("..") entry? ### Why don't we pre-populate an inode for the dot-dot ("..") entry?
@@ -1150,7 +1140,7 @@ See
* https://linux.die.net/man/3/getdents * https://linux.die.net/man/3/getdents
* https://www.unix.com/man-page/osx/2/getdirentries/ * https://www.unix.com/man-page/osx/2/getdirentries/
* https://man.openbsd.org/OpenBSD-5.4/getdirentries.2 * https://man.openbsd.org/OpenBSD-5.4/getdirentries.2
* https://github.com/golang/go/blob/go1.20/src/syscall/dirent.go#L60-L102 * https://github.com/golang/go/blob/go1.24.0/src/syscall/dirent.go#L57-L101
* https://go-review.googlesource.com/c/go/+/507915 * https://go-review.googlesource.com/c/go/+/507915
## sys.Walltime and Nanotime ## sys.Walltime and Nanotime
@@ -1201,7 +1191,7 @@ See https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md
WebAssembly time imports do not have the same concern. In fact even Go's WebAssembly time imports do not have the same concern. In fact even Go's
imports for clocks split walltime from nanotime readings. imports for clocks split walltime from nanotime readings.
See https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L243-L255 See https://github.com/golang/go/blob/go1.24.0/lib/wasm/wasm_exec.js#L258-L268
Finally, Go's clock is not an interface. WebAssembly users who want determinism Finally, Go's clock is not an interface. WebAssembly users who want determinism
or security need to be able to substitute an alternative clock implementation or security need to be able to substitute an alternative clock implementation
@@ -1215,13 +1205,13 @@ value. For now, we return fixed values of 1us for realtime and 1ns for monotonic
often lower precision than monotonic clocks. In the future, this could be improved by having OS+arch specific assembly often lower precision than monotonic clocks. In the future, this could be improved by having OS+arch specific assembly
to make syscalls. to make syscalls.
For example, Go implements time.Now for linux-amd64 with this [assembly](https://github.com/golang/go/blob/go1.20/src/runtime/time_linux_amd64.s). For example, Go implements time.Now for linux-amd64 with this [assembly](https://github.com/golang/go/blob/go1.24.0/src/runtime/time_linux_amd64.s).
Because retrieving resolution is not generally called often, unlike getting time, it could be appropriate to only Because retrieving resolution is not generally called often, unlike getting time, it could be appropriate to only
implement the fallback logic that does not use VDSO (executing syscalls in user mode). The syscall for clock_getres implement the fallback logic that does not use VDSO (executing syscalls in user mode). The syscall for clock_getres
is 229 and should be usable. https://pkg.go.dev/syscall#pkg-constants. is 229 and should be usable. https://pkg.go.dev/syscall#pkg-constants.
If implementing similar for Windows, [mingw](https://github.com/mirror/mingw-w64/blob/6a0e9165008f731bccadfc41a59719cf7c8efc02/mingw-w64-libraries/winpthreads/src/clock.c#L77 If implementing similar for Windows, [mingw](https://github.com/mirror/mingw-w64/blob/v12.0.0/mingw-w64-libraries/winpthreads/src/clock.c#L54)
) is often a good source to find the Windows API calls that correspond is often a good source to find the Windows API calls that correspond
to a POSIX method. to a POSIX method.
Writing assembly would allow making syscalls without CGO, but comes with the cost that it will require implementations Writing assembly would allow making syscalls without CGO, but comes with the cost that it will require implementations
@@ -1490,7 +1480,7 @@ This is due to the same reason for the limitation on the number of functions abo
While the the spec does not clarify a limitation of function stack values, wazero limits this to 2^27 = 134,217,728. While the the spec does not clarify a limitation of function stack values, wazero limits this to 2^27 = 134,217,728.
The reason is that we internally represent all the values as 64-bit integers regardless of its types (including f32, f64), and 2^27 values means The reason is that we internally represent all the values as 64-bit integers regardless of its types (including f32, f64), and 2^27 values means
1 GiB = (2^30). 1 GiB is the reasonable for most applications [as we see a Goroutine has 250 MB as a limit on the stack for 32-bit arch](https://github.com/golang/go/blob/go1.20/src/runtime/proc.go#L152-L159), considering that WebAssembly is (currently) 32-bit environment. 1 GiB = (2^30). 1 GiB is the reasonable for most applications [as we see a Goroutine has 250 MB as a limit on the stack for 32-bit arch](https://github.com/golang/go/blob/go1.24.0/src/runtime/proc.go#L154-L161), considering that WebAssembly is (currently) 32-bit environment.
All the functions are statically analyzed at module instantiation phase, and if a function can potentially reach this limit, an error is returned. All the functions are statically analyzed at module instantiation phase, and if a function can potentially reach this limit, an error is returned.
@@ -1523,8 +1513,8 @@ since it tries to interrupt the execution of Goroutine at any point of function,
Fortunately, our runtime-generated machine codes do not need to take the async preemption into account. Fortunately, our runtime-generated machine codes do not need to take the async preemption into account.
All the assembly codes are entered via the trampoline implemented as Go Assembler Function (e.g. [arch_amd64.s](./arch_amd64.s)), All the assembly codes are entered via the trampoline implemented as Go Assembler Function (e.g. [arch_amd64.s](./arch_amd64.s)),
and as of Go 1.20, these assembler functions are considered as _unsafe_ for async preemption: and as of Go 1.20, these assembler functions are considered as _unsafe_ for async preemption:
- https://github.com/golang/go/blob/go1.20rc1/src/runtime/preempt.go#L406-L407 - https://github.com/golang/go/blob/go1.24.0/src/runtime/preempt.go#L407-L408
- https://github.com/golang/go/blob/9f0234214473dfb785a5ad84a8fc62a6a395cbc3/src/runtime/traceback.go#L227 - https://github.com/golang/go/blob/go1.24.0/src/runtime/traceback.go#L350
From the Go runtime point of view, the execution of runtime-generated machine codes is considered as a part of From the Go runtime point of view, the execution of runtime-generated machine codes is considered as a part of
that trampoline function. Therefore, runtime-generated machine code is also correctly considered unsafe for async preemption. that trampoline function. Therefore, runtime-generated machine code is also correctly considered unsafe for async preemption.
@@ -1582,6 +1572,6 @@ In lieu of formal documentation, we infer this pattern works from other sources
* `sync.WaitGroup` by definition must support calling `Add` from other goroutines. Internally, it uses atomics. * `sync.WaitGroup` by definition must support calling `Add` from other goroutines. Internally, it uses atomics.
* rsc in golang/go#5045 writes "atomics guarantee sequential consistency among the atomic variables". * rsc in golang/go#5045 writes "atomics guarantee sequential consistency among the atomic variables".
See https://github.com/golang/go/blob/go1.20/src/sync/waitgroup.go#L64 See https://github.com/golang/go/blob/go1.24.0/src/sync/waitgroup.go#L76
See https://github.com/golang/go/issues/5045#issuecomment-252730563 See https://github.com/golang/go/issues/5045#issuecomment-252730563
See https://www.youtube.com/watch?v=VmrEG-3bWyM See https://www.youtube.com/watch?v=VmrEG-3bWyM

View File

@@ -76,19 +76,13 @@ Please give us a [star][10] if you end up using wazero!
### Go ### Go
wazero has no dependencies except Go, so the only source of conflict in your wazero has no dependencies except Go and [`x/sys`][12], so the only source of
project's use of wazero is the Go version. conflict in your project's use of wazero is the Go version.
wazero follows the same version policy as Go's [Release Policy][5]: two wazero follows the same version policy as Go's [Release Policy][5]: two
versions. wazero will ensure these versions work and bugs are valid if there's versions. wazero will ensure these versions work and bugs are valid if there's
an issue with a current Go version. an issue with a current Go version.
Additionally, wazero intentionally delays usage of language or standard library
features one additional version. For example, when Go 1.29 is released, wazero
can use language features or standard libraries added in 1.27. This is a
convenience for embedders who have a slower version policy than Go. However,
only supported Go versions may be used to raise support issues.
### Platform ### Platform
wazero has two runtime modes: Interpreter and Compiler. The only supported operating wazero has two runtime modes: Interpreter and Compiler. The only supported operating
@@ -138,3 +132,4 @@ wazero is a registered trademark of Tetrate.io, Inc. in the United States and/or
[9]: https://wazero.io/community/users/ [9]: https://wazero.io/community/users/
[10]: https://github.com/wazero/wazero/stargazers [10]: https://github.com/wazero/wazero/stargazers
[11]: https://github.com/wazero/wazero/issues/2393 [11]: https://github.com/wazero/wazero/issues/2393
[12]: https://pkg.go.dev/golang.org/x/sys

View File

@@ -1,4 +1,4 @@
//go:build !plan9 && !aix //go:build !(plan9 || aix)
package sys package sys

View File

@@ -1,61 +1,32 @@
package sys package sys
import "syscall" import "golang.org/x/sys/windows"
// These are errors not defined in the syscall package. They are prefixed with
// underscore to avoid exporting them.
//
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
const (
// _ERROR_INVALID_HANDLE is a Windows error returned by syscall.Write
// instead of syscall.EBADF
_ERROR_INVALID_HANDLE = syscall.Errno(6)
// _ERROR_INVALID_NAME is a Windows error returned by open when a file
// path has a trailing slash
_ERROR_INVALID_NAME = syscall.Errno(0x7B)
// _ERROR_NEGATIVE_SEEK is a Windows error returned by os.Truncate
// instead of syscall.EINVAL
_ERROR_NEGATIVE_SEEK = syscall.Errno(0x83)
// _ERROR_DIRECTORY is a Windows error returned by syscall.Rmdir
// instead of syscall.ENOTDIR
_ERROR_DIRECTORY = syscall.Errno(0x10B)
// _ERROR_NOT_A_REPARSE_POINT is a Windows error returned by os.Readlink
// instead of syscall.EINVAL
_ERROR_NOT_A_REPARSE_POINT = syscall.Errno(0x1126)
// _ERROR_INVALID_SOCKET is a Windows error returned by winsock_select
// when a given handle is not a socket.
_ERROR_INVALID_SOCKET = syscall.Errno(0x2736)
)
func errorToErrno(err error) Errno { func errorToErrno(err error) Errno {
switch err := err.(type) { switch err := err.(type) {
case Errno: case Errno:
return err return err
case syscall.Errno: case windows.Errno:
// Note: In windows, _ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR // Note: In windows, _ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR
switch err { switch err {
case syscall.ERROR_ALREADY_EXISTS: case windows.ERROR_ALREADY_EXISTS, windows.ERROR_FILE_EXISTS:
return EEXIST return EEXIST
case _ERROR_DIRECTORY: case windows.ERROR_DIRECTORY:
// ERROR_DIRECTORY is returned by syscall.Rmdir.
return ENOTDIR return ENOTDIR
case syscall.ERROR_DIR_NOT_EMPTY: case windows.ERROR_DIR_NOT_EMPTY:
return ENOTEMPTY return ENOTEMPTY
case syscall.ERROR_FILE_EXISTS: case windows.ERROR_INVALID_HANDLE, windows.WSAENOTSOCK, windows.ERROR_ACCESS_DENIED:
return EEXIST // WSAENOTSOCK is returned by winsock_select when a given handle is not a socket.
case _ERROR_INVALID_HANDLE, _ERROR_INVALID_SOCKET:
return EBADF
case syscall.ERROR_ACCESS_DENIED:
// POSIX read and write functions expect EBADF, not EACCES when not // POSIX read and write functions expect EBADF, not EACCES when not
// open for reading or writing. // open for reading or writing.
return EBADF return EBADF
case syscall.ERROR_PRIVILEGE_NOT_HELD: case windows.ERROR_PRIVILEGE_NOT_HELD:
return EPERM return EPERM
case _ERROR_NEGATIVE_SEEK, _ERROR_INVALID_NAME, _ERROR_NOT_A_REPARSE_POINT: case windows.ERROR_NEGATIVE_SEEK, windows.ERROR_NOT_A_REPARSE_POINT, windows.ERROR_INVALID_NAME:
// ERROR_NEGATIVE_SEEK is returned by os.Truncate.
// ERROR_NOT_A_REPARSE_POINT is returned by os.Readlink.
// ERROR_INVALID_NAME is returned by open when a file path has a trailing slash.
return EINVAL return EINVAL
} }
errno, _ := syscallToErrno(err) errno, _ := syscallToErrno(err)

View File

@@ -84,6 +84,10 @@ func (e *engine) deleteCompiledFunctions(module *wasm.Module) {
func (e *engine) addCompiledFunctions(module *wasm.Module, fs []compiledFunction) { func (e *engine) addCompiledFunctions(module *wasm.Module, fs []compiledFunction) {
e.mux.Lock() e.mux.Lock()
defer e.mux.Unlock() defer e.mux.Unlock()
if c, ok := e.compiledFunctions[module.ID]; ok {
c.refCount++
return
}
e.compiledFunctions[module.ID] = &compiledFunctionWithCount{funcs: fs, refCount: 1} e.compiledFunctions[module.ID] = &compiledFunctionWithCount{funcs: fs, refCount: 1}
} }
@@ -250,7 +254,7 @@ func functionFromUintptr(ptr uintptr) *function {
// //
// For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr" // For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr"
// subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation" // subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation"
// https://github.com/golang/go/blob/1ce7fcf139417d618c2730010ede2afb41664211/src/runtime/checkptr.go#L69 // https://github.com/golang/go/blob/go1.24.0/src/runtime/checkptr.go#L69
var wrapped *uintptr = &ptr var wrapped *uintptr = &ptr
return *(**function)(unsafe.Pointer(wrapped)) return *(**function)(unsafe.Pointer(wrapped))
} }

View File

@@ -7,7 +7,7 @@ import (
) )
// For the details of the ABI, see: // For the details of the ABI, see:
// https://github.com/golang/go/blob/49d42128fd8594c172162961ead19ac95e247d24/src/cmd/compile/abi-internal.md#amd64-architecture // https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/abi-internal.md#amd64-architecture
var ( var (
intArgResultRegs = []regalloc.RealReg{rax, rbx, rcx, rdi, rsi, r8, r9, r10, r11} intArgResultRegs = []regalloc.RealReg{rax, rbx, rcx, rdi, rsi, r8, r9, r10, r11}

View File

@@ -17,7 +17,7 @@ import (
// NewBackend returns a new backend for arm64. // NewBackend returns a new backend for arm64.
func NewBackend() backend.Machine { func NewBackend() backend.Machine {
m := &machine{ m := &machine{
cpuFeatures: platform.CpuFeatures(), cpuFeatures: platform.CpuFeatures,
regAlloc: regalloc.NewAllocator[*instruction, *labelPosition, *regAllocFn](regInfo), regAlloc: regalloc.NewAllocator[*instruction, *labelPosition, *regAllocFn](regInfo),
spillSlots: map[regalloc.VRegID]int64{}, spillSlots: map[regalloc.VRegID]int64{},
amodePool: wazevoapi.NewPool[amode](nil), amodePool: wazevoapi.NewPool[amode](nil),
@@ -1067,7 +1067,7 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
dst := m.c.VRegOf(instr.Return()) dst := m.c.VRegOf(instr.Return())
// At this point, the ptr is ensured to be aligned, so using a normal load is atomic. // At this point, the ptr is ensured to be aligned, so using a normal load is atomic.
// https://github.com/golang/go/blob/adead1a93f472affa97c494ef19f2f492ee6f34a/src/runtime/internal/atomic/atomic_amd64.go#L30 // https://github.com/golang/go/blob/go1.24.0/src/internal/runtime/atomic/atomic_amd64.go#L29
mem := newOperandMem(m.lowerToAddressMode(ptr, 0)) mem := newOperandMem(m.lowerToAddressMode(ptr, 0))
load := m.allocateInstr() load := m.allocateInstr()
switch size { switch size {
@@ -1410,7 +1410,7 @@ func (m *machine) lowerVconst(dst regalloc.VReg, lo, hi uint64) {
} }
func (m *machine) lowerCtz(instr *ssa.Instruction) { func (m *machine) lowerCtz(instr *ssa.Instruction) {
if m.cpuFeatures.HasExtra(platform.CpuExtraFeatureAmd64ABM) { if m.cpuFeatures.Has(platform.CpuFeatureAmd64BMI1) {
m.lowerUnaryRmR(instr, unaryRmROpcodeTzcnt) m.lowerUnaryRmR(instr, unaryRmROpcodeTzcnt)
} else { } else {
// On processors that do not support TZCNT, the BSF instruction is // On processors that do not support TZCNT, the BSF instruction is
@@ -1464,7 +1464,7 @@ func (m *machine) lowerCtz(instr *ssa.Instruction) {
} }
func (m *machine) lowerClz(instr *ssa.Instruction) { func (m *machine) lowerClz(instr *ssa.Instruction) {
if m.cpuFeatures.HasExtra(platform.CpuExtraFeatureAmd64ABM) { if m.cpuFeatures.Has(platform.CpuFeatureAmd64ABM) {
m.lowerUnaryRmR(instr, unaryRmROpcodeLzcnt) m.lowerUnaryRmR(instr, unaryRmROpcodeLzcnt)
} else { } else {
// On processors that do not support LZCNT, we combine BSR (calculating // On processors that do not support LZCNT, we combine BSR (calculating
@@ -1892,8 +1892,8 @@ func (m *machine) lowerCall(si *ssa.Instruction) {
if isMemmove { if isMemmove {
// Go's memmove *might* use all xmm0-xmm15, so we need to release them. // Go's memmove *might* use all xmm0-xmm15, so we need to release them.
// https://github.com/golang/go/blob/49d42128fd8594c172162961ead19ac95e247d24/src/cmd/compile/abi-internal.md#architecture-specifics // https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/abi-internal.md#architecture-specifics
// https://github.com/golang/go/blob/49d42128fd8594c172162961ead19ac95e247d24/src/runtime/memmove_amd64.s#L271-L286 // https://github.com/golang/go/blob/go1.24.0/src/runtime/memmove_amd64.s#L286-L301
for i := regalloc.RealReg(0); i < 16; i++ { for i := regalloc.RealReg(0); i < 16; i++ {
m.insert(m.allocateInstr().asDefineUninitializedReg(regInfo.RealRegToVReg[xmm0+i])) m.insert(m.allocateInstr().asDefineUninitializedReg(regInfo.RealRegToVReg[xmm0+i]))
} }

View File

@@ -7,7 +7,7 @@ import (
) )
// References: // References:
// * https://github.com/golang/go/blob/49d42128fd8594c172162961ead19ac95e247d24/src/cmd/compile/abi-internal.md#arm64-architecture // * https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/abi-internal.md#arm64-architecture
// * https://developer.arm.com/documentation/102374/0101/Procedure-Call-Standard // * https://developer.arm.com/documentation/102374/0101/Procedure-Call-Standard
var ( var (

View File

@@ -96,7 +96,7 @@ func (m *machine) lowerConstantI32(dst regalloc.VReg, c int32) {
func (m *machine) lowerConstantI64(dst regalloc.VReg, c int64) { func (m *machine) lowerConstantI64(dst regalloc.VReg, c int64) {
// Following the logic here: // Following the logic here:
// https://github.com/golang/go/blob/release-branch.go1.15/src/cmd/internal/obj/arm64/asm7.go#L1798-L1852 // https://github.com/golang/go/blob/go1.24.0/src/cmd/internal/obj/arm64/asm7.go#L2161-L2215
if c >= 0 && (c <= 0xfff || (c&0xfff) == 0 && (uint64(c>>12) <= 0xfff)) { if c >= 0 && (c <= 0xfff || (c&0xfff) == 0 && (uint64(c>>12) <= 0xfff)) {
if isBitMaskImmediate(uint64(c), true) { if isBitMaskImmediate(uint64(c), true) {
m.lowerConstViaBitMaskImmediate(uint64(c), dst, true) m.lowerConstViaBitMaskImmediate(uint64(c), dst, true)
@@ -188,7 +188,7 @@ func const16bitAligned(v int64) (ret int) {
// load64bitConst loads a 64-bit constant into the register, following the same logic to decide how to load large 64-bit // load64bitConst loads a 64-bit constant into the register, following the same logic to decide how to load large 64-bit
// consts as in the Go assembler. // consts as in the Go assembler.
// //
// See https://github.com/golang/go/blob/release-branch.go1.15/src/cmd/internal/obj/arm64/asm7.go#L6632-L6759 // See https://github.com/golang/go/blob/go1.24.0/src/cmd/internal/obj/arm64/asm7.go#L7555-L7682
func (m *machine) load64bitConst(c int64, dst regalloc.VReg) { func (m *machine) load64bitConst(c int64, dst regalloc.VReg) {
var bits [4]uint64 var bits [4]uint64
var zeros, negs int var zeros, negs int

View File

@@ -160,8 +160,8 @@ var (
// tmpReg is used to perform spill/load on large stack offsets, and load large constants. // tmpReg is used to perform spill/load on large stack offsets, and load large constants.
// Therefore, be cautious to use this register in the middle of the compilation, especially before the register allocation. // Therefore, be cautious to use this register in the middle of the compilation, especially before the register allocation.
// This is the same as golang/go, but it's only described in the source code: // This is the same as golang/go, but it's only described in the source code:
// https://github.com/golang/go/blob/18e17e2cb12837ea2c8582ecdb0cc780f49a1aac/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go#L59 // https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go#L59
// https://github.com/golang/go/blob/18e17e2cb12837ea2c8582ecdb0cc780f49a1aac/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go#L13-L15 // https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go#L13-L15
tmpRegVReg = regalloc.FromRealReg(tmp, regalloc.RegTypeInt) tmpRegVReg = regalloc.FromRealReg(tmp, regalloc.RegTypeInt)
v28VReg = regalloc.FromRealReg(v28, regalloc.RegTypeFloat) v28VReg = regalloc.FromRealReg(v28, regalloc.RegTypeFloat)
v29VReg = regalloc.FromRealReg(v29, regalloc.RegTypeFloat) v29VReg = regalloc.FromRealReg(v29, regalloc.RegTypeFloat)

View File

@@ -150,7 +150,7 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene
if err != nil { if err != nil {
return err return err
} }
if err = e.addCompiledModule(module, cm); err != nil { if cm, err = e.addCompiledModule(module, cm); err != nil {
return err return err
} }
@@ -372,7 +372,7 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
relocator.resolveRelocations(machine, executable, importedFns) relocator.resolveRelocations(machine, executable, importedFns)
if err = platform.MprotectRX(executable); err != nil { if err = platform.MprotectCodeSegment(executable); err != nil {
return nil, err return nil, err
} }
cm.sharedFunctions = e.sharedFunctions cm.sharedFunctions = e.sharedFunctions
@@ -615,7 +615,7 @@ func (e *engine) compileHostModule(ctx context.Context, module *wasm.Module, lis
wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets) wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets)
} }
if err = platform.MprotectRX(executable); err != nil { if err = platform.MprotectCodeSegment(executable); err != nil {
return nil, err return nil, err
} }
e.setFinalizer(cm.executables, executablesFinalizer) e.setFinalizer(cm.executables, executablesFinalizer)
@@ -877,7 +877,7 @@ func mmapExecutable(src []byte) []byte {
copy(executable, src) copy(executable, src)
if err = platform.MprotectRX(executable); err != nil { if err = platform.MprotectCodeSegment(executable); err != nil {
panic(err) panic(err)
} }
return executable return executable

View File

@@ -32,7 +32,7 @@ func fileCacheKey(m *wasm.Module) (ret filecache.Key) {
s.Write(magic) s.Write(magic)
// Write the CPU features so that we can cache the compiled module for the same CPU. // Write the CPU features so that we can cache the compiled module for the same CPU.
// This prevents the incompatible CPU features from being used. // This prevents the incompatible CPU features from being used.
cpu := platform.CpuFeatures().Raw() cpu := platform.CpuFeatures.Raw()
// Reuse the `ret` buffer to write the first 8 bytes of the CPU features so that we can avoid the allocation. // Reuse the `ret` buffer to write the first 8 bytes of the CPU features so that we can avoid the allocation.
binary.LittleEndian.PutUint64(ret[:8], cpu) binary.LittleEndian.PutUint64(ret[:8], cpu)
s.Write(ret[:8]) s.Write(ret[:8])
@@ -41,10 +41,10 @@ func fileCacheKey(m *wasm.Module) (ret filecache.Key) {
return return
} }
func (e *engine) addCompiledModule(module *wasm.Module, cm *compiledModule) (err error) { func (e *engine) addCompiledModule(module *wasm.Module, cm *compiledModule) (c *compiledModule, err error) {
e.addCompiledModuleToMemory(module, cm) c = e.addCompiledModuleToMemory(module, cm)
if !module.IsHostModule && e.fileCache != nil { if !module.IsHostModule && e.fileCache != nil {
err = e.addCompiledModuleToCache(module, cm) err = e.addCompiledModuleToCache(module, c)
} }
return return
} }
@@ -84,13 +84,18 @@ func (e *engine) getCompiledModule(module *wasm.Module, listeners []experimental
return return
} }
func (e *engine) addCompiledModuleToMemory(m *wasm.Module, cm *compiledModule) { func (e *engine) addCompiledModuleToMemory(m *wasm.Module, cm *compiledModule) *compiledModule {
e.mux.Lock() e.mux.Lock()
defer e.mux.Unlock() defer e.mux.Unlock()
if c, ok := e.compiledModules[m.ID]; ok {
c.refCount++
return c.compiledModule
}
e.compiledModules[m.ID] = &compiledModuleWithCount{compiledModule: cm, refCount: 1} e.compiledModules[m.ID] = &compiledModuleWithCount{compiledModule: cm, refCount: 1}
if len(cm.executable) > 0 { if len(cm.executable) > 0 {
e.addCompiledModuleToSortedList(cm) e.addCompiledModuleToSortedList(cm)
} }
return cm
} }
func (e *engine) getCompiledModuleFromMemory(module *wasm.Module, increaseRefCount bool) (cm *compiledModule, ok bool) { func (e *engine) getCompiledModuleFromMemory(module *wasm.Module, increaseRefCount bool) (cm *compiledModule, ok bool) {
@@ -252,7 +257,7 @@ func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser) (cm *
return nil, false, fmt.Errorf("compilationcache: checksum mismatch (expected %d, got %d)", expected, checksum) return nil, false, fmt.Errorf("compilationcache: checksum mismatch (expected %d, got %d)", expected, checksum)
} }
if err = platform.MprotectRX(executable); err != nil { if err = platform.MprotectCodeSegment(executable); err != nil {
return nil, false, err return nil, false, err
} }
cm.executable = executable cm.executable = executable

View File

@@ -1,5 +1,3 @@
//go:build amd64 && !tinygo
package wazevo package wazevo
import _ "unsafe" import _ "unsafe"

View File

@@ -1,5 +1,3 @@
//go:build arm64 && !tinygo
package wazevo package wazevo
import _ "unsafe" import _ "unsafe"

View File

@@ -1,4 +1,4 @@
//go:build (!arm64 && !amd64) || tinygo //go:build !(arm64 || amd64)
package wazevo package wazevo

View File

@@ -8,8 +8,8 @@ func PtrFromUintptr[T any](ptr uintptr) *T {
// Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector. // Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector.
// //
// For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr" // For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr"
// subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation" // subroutine warns as "checkptr: pointer arithmetic result points to invalid allocation"
// https://github.com/golang/go/blob/1ce7fcf139417d618c2730010ede2afb41664211/src/runtime/checkptr.go#L69 // https://github.com/golang/go/blob/go1.24.0/src/runtime/checkptr.go#L69
var wrapped *uintptr = &ptr var wrapped *uintptr = &ptr
return *(**T)(unsafe.Pointer(wrapped)) return *(**T)(unsafe.Pointer(wrapped))
} }

View File

@@ -109,7 +109,7 @@ func LoadUint32(buf []byte) (ret uint32, bytesRead uint64, err error) {
} }
func decodeUint32(next nextByte) (ret uint32, bytesRead uint64, err error) { func decodeUint32(next nextByte) (ret uint32, bytesRead uint64, err error) {
// Derived from https://github.com/golang/go/blob/go1.20/src/encoding/binary/varint.go // Derived from https://github.com/golang/go/blob/go1.24.0/src/encoding/binary/varint.go
// with the modification on the overflow handling tailored for 32-bits. // with the modification on the overflow handling tailored for 32-bits.
var s uint32 var s uint32
for i := 0; i < maxVarintLen32; i++ { for i := 0; i < maxVarintLen32; i++ {
@@ -124,7 +124,7 @@ func decodeUint32(next nextByte) (ret uint32, bytesRead uint64, err error) {
} }
return ret | uint32(b)<<s, uint64(i) + 1, nil return ret | uint32(b)<<s, uint64(i) + 1, nil
} }
ret |= (uint32(b) & 0x7f) << s ret |= uint32(b&0x7f) << s
s += 7 s += 7
} }
return 0, 0, errOverflow32 return 0, 0, errOverflow32
@@ -136,7 +136,7 @@ func LoadUint64(buf []byte) (ret uint64, bytesRead uint64, err error) {
return 0, 0, io.EOF return 0, 0, io.EOF
} }
// Derived from https://github.com/golang/go/blob/go1.20/src/encoding/binary/varint.go // Derived from https://github.com/golang/go/blob/go1.24.0/src/encoding/binary/varint.go
var s uint64 var s uint64
for i := 0; i < maxVarintLen64; i++ { for i := 0; i < maxVarintLen64; i++ {
if i >= bufLen { if i >= bufLen {
@@ -150,7 +150,7 @@ func LoadUint64(buf []byte) (ret uint64, bytesRead uint64, err error) {
} }
return ret | uint64(b)<<s, uint64(i) + 1, nil return ret | uint64(b)<<s, uint64(i) + 1, nil
} }
ret |= (uint64(b) & 0x7f) << s ret |= uint64(b&0x7f) << s
s += 7 s += 7
} }
return 0, 0, errOverflow64 return 0, 0, errOverflow64

View File

@@ -1,35 +1,26 @@
package platform package platform
// CpuFeatureFlags exposes methods for querying CPU capabilities // CpuFeatureFlags exposes methods for querying CPU capabilities
type CpuFeatureFlags interface { type CpuFeatureFlags uint64
// Has returns true when the specified flag (represented as uint64) is supported
Has(cpuFeature CpuFeature) bool
// HasExtra returns true when the specified extraFlag (represented as uint64) is supported
HasExtra(cpuFeature CpuFeature) bool
// Raw returns the raw bitset that represents CPU features used by wazero. This can be used for cache keying.
// For now, we only use four features, so uint64 is enough.
Raw() uint64
}
type CpuFeature uint64
const ( const (
// CpuFeatureAmd64SSE3 is the flag to query CpuFeatureFlags.Has for SSEv3 capabilities on amd64
CpuFeatureAmd64SSE3 CpuFeature = 1
// CpuFeatureAmd64SSE4_1 is the flag to query CpuFeatureFlags.Has for SSEv4.1 capabilities on amd64 // CpuFeatureAmd64SSE4_1 is the flag to query CpuFeatureFlags.Has for SSEv4.1 capabilities on amd64
CpuFeatureAmd64SSE4_1 CpuFeature = 1 << 19 CpuFeatureAmd64SSE4_1 = 1 << iota
// CpuFeatureAmd64SSE4_2 is the flag to query CpuFeatureFlags.Has for SSEv4.2 capabilities on amd64 // CpuFeatureAmd64BMI1 is the flag to query CpuFeatureFlags.Has for Bit Manipulation Instruction Set 1 (e.g. TZCNT) on amd64
CpuFeatureAmd64SSE4_2 CpuFeature = 1 << 20 CpuFeatureAmd64BMI1
// Note: when adding new features, ensure that the feature is included in CpuFeatureFlags.Raw. // CpuExtraFeatureABM is the flag to query CpuFeatureFlags.Has for Advanced Bit Manipulation capabilities (e.g. LZCNT) on amd64
) CpuFeatureAmd64ABM
const (
// CpuExtraFeatureAmd64ABM is the flag to query CpuFeatureFlags.HasExtra for Advanced Bit Manipulation capabilities (e.g. LZCNT) on amd64
CpuExtraFeatureAmd64ABM CpuFeature = 1 << 5
// Note: when adding new features, ensure that the feature is included in CpuFeatureFlags.Raw.
) )
const ( const (
// CpuFeatureArm64Atomic is the flag to query CpuFeatureFlags.Has for Large System Extensions capabilities on arm64 // CpuFeatureArm64Atomic is the flag to query CpuFeatureFlags.Has for Large System Extensions capabilities on arm64
CpuFeatureArm64Atomic CpuFeature = 1 << 21 CpuFeatureArm64Atomic CpuFeatureFlags = 1 << iota
) )
func (c CpuFeatureFlags) Has(f CpuFeatureFlags) bool {
return c&f != 0
}
func (c CpuFeatureFlags) Raw() uint64 {
return uint64(c)
}

View File

@@ -1,82 +1,22 @@
//go:build gc
package platform package platform
import "sync" import "golang.org/x/sys/cpu"
// CpuFeatures exposes the capabilities for this CPU, queried via the Has, HasExtra methods. // CpuFeatures exposes the capabilities for this CPU, queried via the Has method.
var CpuFeatures = sync.OnceValue(loadCpuFeatureFlags) var CpuFeatures = loadCpuFeatureFlags()
// cpuFeatureFlags implements CpuFeatureFlags interface. func loadCpuFeatureFlags() (flags CpuFeatureFlags) {
type cpuFeatureFlags struct { if cpu.X86.HasSSE41 {
flags uint64 flags |= CpuFeatureAmd64SSE4_1
extraFlags uint64
}
// cpuid exposes the CPUID instruction to the Go layer (https://www.amd.com/system/files/TechDocs/25481.pdf)
// implemented in cpuid_amd64.s
func cpuid(arg1, arg2 uint32) (eax, ebx, ecx, edx uint32)
// cpuidAsBitmap combines the result of invoking cpuid to uint64 bitmap.
func cpuidAsBitmap(arg1, arg2 uint32) uint64 {
_ /* eax */, _ /* ebx */, ecx, edx := cpuid(arg1, arg2)
return (uint64(edx) << 32) | uint64(ecx)
}
// loadStandardRange load flags from the standard range, panics otherwise.
func loadStandardRange(id uint32) uint64 {
// ensure that the id is in the valid range, returned by cpuid(0,0)
maxRange, _, _, _ := cpuid(0, 0)
if id > maxRange {
panic("cannot query standard CPU flags")
} }
return cpuidAsBitmap(id, 0) if cpu.X86.HasBMI1 {
} flags |= CpuFeatureAmd64BMI1
// loadStandardRange load flags from the extended range, panics otherwise.
func loadExtendedRange(id uint32) uint64 {
// ensure that the id is in the valid range, returned by cpuid(0x80000000,0)
maxRange, _, _, _ := cpuid(0x80000000, 0)
if id > maxRange {
panic("cannot query extended CPU flags")
} }
return cpuidAsBitmap(id, 0) // x/sys/cpu does not track the ABM explicitly.
} // LZCNT combined with BMI1 and BMI2 completes the expanded ABM instruction set.
// Intel includes LZCNT in BMI1, and all AMD CPUs with POPCNT also have LZCNT.
func loadCpuFeatureFlags() CpuFeatureFlags { if cpu.X86.HasBMI1 && cpu.X86.HasBMI2 && cpu.X86.HasPOPCNT {
return &cpuFeatureFlags{ flags |= CpuFeatureAmd64ABM
flags: loadStandardRange(1),
extraFlags: loadExtendedRange(0x80000001),
} }
} return
// Has implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) Has(cpuFeature CpuFeature) bool {
return (f.flags & uint64(cpuFeature)) != 0
}
// HasExtra implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) HasExtra(cpuFeature CpuFeature) bool {
return (f.extraFlags & uint64(cpuFeature)) != 0
}
// Raw implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) Raw() uint64 {
// Below, we only set bits for the features we care about,
// instead of setting all the unnecessary bits obtained from the
// CPUID instruction.
var ret uint64
if f.Has(CpuFeatureAmd64SSE3) {
ret = 1 << 0
}
if f.Has(CpuFeatureAmd64SSE4_1) {
ret |= 1 << 1
}
if f.Has(CpuFeatureAmd64SSE4_2) {
ret |= 1 << 2
}
if f.HasExtra(CpuExtraFeatureAmd64ABM) {
ret |= 1 << 3
}
return ret
} }

View File

@@ -1,16 +0,0 @@
//go:build gc
#include "textflag.h"
// lifted from github.com/intel-go/cpuid and src/internal/cpu/cpu_x86.s
// func cpuid(arg1, arg2 uint32) (eax, ebx, ecx, edx uint32)
TEXT ·cpuid(SB), NOSPLIT, $0-24
MOVL arg1+0(FP), AX
MOVL arg2+4(FP), CX
CPUID
MOVL AX, eax+8(FP)
MOVL BX, ebx+12(FP)
MOVL CX, ecx+16(FP)
MOVL DX, edx+20(FP)
RET

View File

@@ -1,28 +1,15 @@
//go:build gc
package platform package platform
import ( import (
"runtime" "runtime"
"sync"
"golang.org/x/sys/cpu"
) )
// CpuFeatures exposes the capabilities for this CPU, queried via the Has, HasExtra methods. // CpuFeatures exposes the capabilities for this CPU, queried via the Has method.
var CpuFeatures = sync.OnceValue(loadCpuFeatureFlags) var CpuFeatures = loadCpuFeatureFlags()
// cpuFeatureFlags implements CpuFeatureFlags interface. func loadCpuFeatureFlags() (flags CpuFeatureFlags) {
type cpuFeatureFlags struct {
isar0 uint64
isar1 uint64
}
// implemented in cpuid_arm64.s
func getisar0() uint64
// implemented in cpuid_arm64.s
func getisar1() uint64
func loadCpuFeatureFlags() CpuFeatureFlags {
switch runtime.GOOS { switch runtime.GOOS {
case "darwin", "windows": case "darwin", "windows":
// These OSes do not allow userland to read the instruction set attribute registers, // These OSes do not allow userland to read the instruction set attribute registers,
@@ -31,44 +18,11 @@ func loadCpuFeatureFlags() CpuFeatureFlags {
// and the M1 is a ARMv8.4. // and the M1 is a ARMv8.4.
// - "windows" requires them from Windows 11, see page 12 // - "windows" requires them from Windows 11, see page 12
// https://download.microsoft.com/download/7/8/8/788bf5ab-0751-4928-a22c-dffdc23c27f2/Minimum%20Hardware%20Requirements%20for%20Windows%2011.pdf // https://download.microsoft.com/download/7/8/8/788bf5ab-0751-4928-a22c-dffdc23c27f2/Minimum%20Hardware%20Requirements%20for%20Windows%2011.pdf
return &cpuFeatureFlags{ flags |= CpuFeatureArm64Atomic
isar0: uint64(CpuFeatureArm64Atomic),
isar1: 0,
}
case "linux", "freebsd":
// These OSes allow userland to read the instruction set attribute registers,
// which is otherwise restricted to EL0:
// https://kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
// See these for contents of the registers:
// https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/ID-AA64ISAR0-EL1--AArch64-Instruction-Set-Attribute-Register-0
// https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/ID-AA64ISAR1-EL1--AArch64-Instruction-Set-Attribute-Register-1
return &cpuFeatureFlags{
isar0: getisar0(),
isar1: getisar1(),
}
default: default:
return &cpuFeatureFlags{} if cpu.ARM64.HasATOMICS {
flags |= CpuFeatureArm64Atomic
}
} }
} return
// Has implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) Has(cpuFeature CpuFeature) bool {
return (f.isar0 & uint64(cpuFeature)) != 0
}
// HasExtra implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) HasExtra(cpuFeature CpuFeature) bool {
return (f.isar1 & uint64(cpuFeature)) != 0
}
// Raw implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) Raw() uint64 {
// Below, we only set bits for the features we care about,
// instead of setting all the unnecessary bits obtained from the
// instruction set attribute registers.
var ret uint64
if f.Has(CpuFeatureArm64Atomic) {
ret = 1 << 0
}
return ret
} }

View File

@@ -1,21 +0,0 @@
//go:build gc
#include "textflag.h"
// lifted from github.com/golang/sys and cpu/cpu_arm64.s
// func getisar0() uint64
TEXT ·getisar0(SB), NOSPLIT, $0-8
// get Instruction Set Attributes 0 into x0
// mrs x0, ID_AA64ISAR0_EL1 = d5380600
WORD $0xd5380600
MOVD R0, ret+0(FP)
RET
// func getisar1() uint64
TEXT ·getisar1(SB), NOSPLIT, $0-8
// get Instruction Set Attributes 1 into x0
// mrs x0, ID_AA64ISAR1_EL1 = d5380620
WORD $0xd5380620
MOVD R0, ret+0(FP)
RET

View File

@@ -1,17 +1,5 @@
//go:build !(amd64 || arm64) || !gc //go:build !(amd64 || arm64)
package platform package platform
var CpuFeatures = func() CpuFeatureFlags { return &cpuFeatureFlags{} } const CpuFeatures CpuFeatureFlags = 0
// cpuFeatureFlags implements CpuFeatureFlags for unsupported platforms.
type cpuFeatureFlags struct{}
// Has implements the same method on the CpuFeatureFlags interface.
func (c *cpuFeatureFlags) Has(cpuFeature CpuFeature) bool { return false }
// HasExtra implements the same method on the CpuFeatureFlags interface.
func (c *cpuFeatureFlags) HasExtra(cpuFeature CpuFeature) bool { return false }
// Raw implements the same method on the CpuFeatureFlags interface.
func (c *cpuFeatureFlags) Raw() uint64 { return 0 }

View File

@@ -6,13 +6,8 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"syscall"
)
const ( "golang.org/x/sys/unix"
// https://man7.org/linux/man-pages/man2/mmap.2.html
__MAP_HUGE_SHIFT = 26
__MAP_HUGETLB = 0x40000
) )
var hugePagesConfigs []hugePagesConfig var hugePagesConfigs []hugePagesConfig
@@ -50,7 +45,7 @@ func init() {
n *= 1024 n *= 1024
hugePagesConfigs = append(hugePagesConfigs, hugePagesConfig{ hugePagesConfigs = append(hugePagesConfigs, hugePagesConfig{
size: int(n), size: int(n),
flag: int(bits.TrailingZeros64(n)<<__MAP_HUGE_SHIFT) | __MAP_HUGETLB, flag: int(bits.TrailingZeros64(n)<<unix.MAP_HUGE_SHIFT) | unix.MAP_HUGETLB,
}) })
} }
@@ -60,15 +55,12 @@ func init() {
} }
func mmapCodeSegment(size int) ([]byte, error) { func mmapCodeSegment(size int) ([]byte, error) {
flag := syscall.MAP_ANON | syscall.MAP_PRIVATE flag := unix.MAP_ANON | unix.MAP_PRIVATE
prot := syscall.PROT_READ | syscall.PROT_WRITE prot := unix.PROT_READ | unix.PROT_WRITE
if noopMprotectRX {
prot = syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC
}
for _, hugePagesConfig := range hugePagesConfigs { for _, hugePagesConfig := range hugePagesConfigs {
if hugePagesConfig.match(size) { if hugePagesConfig.match(size) {
b, err := syscall.Mmap(-1, 0, size, prot, flag|hugePagesConfig.flag) b, err := unix.Mmap(-1, 0, size, prot, flag|hugePagesConfig.flag)
if err != nil { if err != nil {
continue continue
} }
@@ -76,5 +68,5 @@ func mmapCodeSegment(size int) ([]byte, error) {
} }
} }
return syscall.Mmap(-1, 0, size, prot, flag) return unix.Mmap(-1, 0, size, prot, flag)
} }

View File

@@ -1,22 +1,19 @@
// Separated from linux which has support for huge pages. // Separated from linux which has support for huge pages.
//go:build darwin || freebsd || netbsd || dragonfly || solaris
//go:build unix && !linux
package platform package platform
import "syscall" import "golang.org/x/sys/unix"
func mmapCodeSegment(size int) ([]byte, error) { func mmapCodeSegment(size int) ([]byte, error) {
prot := syscall.PROT_READ | syscall.PROT_WRITE return unix.Mmap(
if noopMprotectRX {
prot = syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC
}
return syscall.Mmap(
-1, -1,
0, 0,
size, size,
prot, unix.PROT_READ|unix.PROT_WRITE,
// Anonymous as this is not an actual file, but a memory, // Anonymous as this is not an actual file, but a memory,
// Private as this is in-process memory region. // Private as this is in-process memory region.
syscall.MAP_ANON|syscall.MAP_PRIVATE, unix.MAP_ANON|unix.MAP_PRIVATE,
) )
} }

View File

@@ -1,9 +1,14 @@
//go:build (linux || darwin || freebsd || netbsd || dragonfly || solaris) && !tinygo //go:build unix
package platform package platform
import "syscall" import "golang.org/x/sys/unix"
func munmapCodeSegment(code []byte) error { func munmapCodeSegment(code []byte) error {
return syscall.Munmap(code) return unix.Munmap(code)
}
// MprotectCodeSegment is like unix.Mprotect with RX permission.
func MprotectCodeSegment(b []byte) (err error) {
return unix.Mprotect(b, unix.PROT_READ|unix.PROT_EXEC)
} }

View File

@@ -1,4 +1,4 @@
//go:build !(linux || darwin || freebsd || netbsd || dragonfly || solaris || windows) || tinygo //go:build !(unix || windows)
package platform package platform
@@ -17,6 +17,6 @@ func mmapCodeSegment(size int) ([]byte, error) {
panic(errUnsupported) panic(errUnsupported)
} }
func MprotectRX(b []byte) (err error) { func MprotectCodeSegment(b []byte) (err error) {
panic(errUnsupported) panic(errUnsupported)
} }

View File

@@ -1,63 +1,21 @@
package platform package platform
import ( import (
"fmt"
"syscall"
"unsafe" "unsafe"
)
var ( "golang.org/x/sys/windows"
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procVirtualAlloc = kernel32.NewProc("VirtualAlloc")
procVirtualProtect = kernel32.NewProc("VirtualProtect")
procVirtualFree = kernel32.NewProc("VirtualFree")
)
const (
windows_MEM_COMMIT uintptr = 0x00001000
windows_MEM_RELEASE uintptr = 0x00008000
windows_PAGE_READWRITE uintptr = 0x00000004
windows_PAGE_EXECUTE_READ uintptr = 0x00000020
windows_PAGE_EXECUTE_READWRITE uintptr = 0x00000040
) )
func munmapCodeSegment(code []byte) error { func munmapCodeSegment(code []byte) error {
return freeMemory(code)
}
// allocateMemory commits the memory region via the "VirtualAlloc" function.
// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
func allocateMemory(size uintptr, protect uintptr) (uintptr, error) {
address := uintptr(0) // system determines where to allocate the region.
alloctype := windows_MEM_COMMIT
if r, _, err := procVirtualAlloc.Call(address, size, alloctype, protect); r == 0 {
return 0, fmt.Errorf("compiler: VirtualAlloc error: %w", ensureErr(err))
} else {
return r, nil
}
}
// freeMemory releases the memory region via the "VirtualFree" function.
// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
func freeMemory(code []byte) error {
address := unsafe.Pointer(&code[0]) address := unsafe.Pointer(&code[0])
size := uintptr(0) // size must be 0 because we're using MEM_RELEASE. size := uintptr(0) // size must be 0 because we're using MEM_RELEASE.
freetype := windows_MEM_RELEASE return windows.VirtualFree(uintptr(address), size, windows.MEM_RELEASE)
if r, _, err := procVirtualFree.Call(uintptr(address), size, freetype); r == 0 {
return fmt.Errorf("compiler: VirtualFree error: %w", ensureErr(err))
}
return nil
}
func virtualProtect(address, size, newprotect uintptr, oldprotect *uint32) error {
if r, _, err := procVirtualProtect.Call(address, size, newprotect, uintptr(unsafe.Pointer(oldprotect))); r == 0 {
return fmt.Errorf("compiler: VirtualProtect error: %w", ensureErr(err))
}
return nil
} }
func mmapCodeSegment(size int) ([]byte, error) { func mmapCodeSegment(size int) ([]byte, error) {
p, err := allocateMemory(uintptr(size), windows_PAGE_READWRITE) address := uintptr(0) // system determines where to allocate the region.
p, err := windows.VirtualAlloc(address, uintptr(size),
windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -65,22 +23,9 @@ func mmapCodeSegment(size int) ([]byte, error) {
return unsafe.Slice((*byte)(unsafe.Pointer(p)), size), nil return unsafe.Slice((*byte)(unsafe.Pointer(p)), size), nil
} }
var old = uint32(windows_PAGE_READWRITE) var old = uint32(windows.PAGE_READWRITE)
func MprotectRX(b []byte) (err error) { func MprotectCodeSegment(b []byte) (err error) {
err = virtualProtect(uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), windows_PAGE_EXECUTE_READ, &old) address := unsafe.Pointer(&b[0])
return return windows.VirtualProtect(uintptr(address), uintptr(len(b)), windows.PAGE_EXECUTE_READ, &old)
}
// ensureErr returns syscall.EINVAL when the input error is nil.
//
// We are supposed to use "GetLastError" which is more precise, but it is not safe to execute in goroutines. While
// "GetLastError" is thread-local, goroutines are not pinned to threads.
//
// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
func ensureErr(err error) error {
if err != nil {
return err
}
return syscall.EINVAL
} }

View File

@@ -1,24 +0,0 @@
//go:build (freebsd || netbsd || dragonfly) && !tinygo
package platform
import (
"syscall"
"unsafe"
)
const noopMprotectRX = false
// MprotectRX is like syscall.Mprotect with RX permission, defined locally so that BSD compiles.
func MprotectRX(b []byte) (err error) {
var _p0 unsafe.Pointer
if len(b) > 0 {
_p0 = unsafe.Pointer(&b[0])
}
const prot = syscall.PROT_READ | syscall.PROT_EXEC
_, _, e1 := syscall.Syscall(syscall.SYS_MPROTECT, uintptr(_p0), uintptr(len(b)), uintptr(prot))
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}

View File

@@ -1,12 +0,0 @@
//go:build (linux || darwin) && !tinygo
package platform
import "syscall"
const noopMprotectRX = false
// MprotectRX is like syscall.Mprotect with RX permission.
func MprotectRX(b []byte) (err error) {
return syscall.Mprotect(b, syscall.PROT_READ|syscall.PROT_EXEC)
}

View File

@@ -1,10 +0,0 @@
//go:build solaris && !tinygo
package platform
const noopMprotectRX = true
func MprotectRX(b []byte) error {
// Assume we already called mmap with at least RX.
return nil
}

View File

@@ -1,7 +1,4 @@
// Package platform includes runtime-specific code needed for the compiler or otherwise. // Package platform includes runtime-specific code needed for the compiler or otherwise.
//
// Note: This is a dependency-free alternative to depending on parts of Go's x/sys.
// See /RATIONALE.md for more context.
package platform package platform
import ( import (
@@ -18,16 +15,16 @@ func CompilerSupported() bool {
func CompilerSupports(features api.CoreFeatures) bool { func CompilerSupports(features api.CoreFeatures) bool {
switch runtime.GOOS { switch runtime.GOOS {
case "linux", "darwin", "freebsd", "netbsd", "dragonfly", "windows": case "linux", "darwin", "freebsd", "netbsd", "windows":
if runtime.GOARCH == "arm64" { if runtime.GOARCH == "arm64" {
if features.IsEnabled(experimental.CoreFeaturesThreads) { if features.IsEnabled(experimental.CoreFeaturesThreads) {
return CpuFeatures().Has(CpuFeatureArm64Atomic) return CpuFeatures.Has(CpuFeatureArm64Atomic)
} }
return true return true
} }
fallthrough fallthrough
case "solaris", "illumos": case "dragonfly", "solaris", "illumos":
return runtime.GOARCH == "amd64" && CpuFeatures().Has(CpuFeatureAmd64SSE4_1) return runtime.GOARCH == "amd64" && CpuFeatures.Has(CpuFeatureAmd64SSE4_1)
default: default:
return false return false
} }

View File

@@ -1,7 +0,0 @@
//go:build !cgo && !windows
package platform
func nanotime() int64 {
return nanotimePortable()
}

View File

@@ -1,4 +1,4 @@
//go:build cgo && !windows //go:build !windows
package platform package platform

View File

@@ -4,11 +4,13 @@ package platform
import ( import (
"math/bits" "math/bits"
"syscall"
"time" "time"
"unsafe" "unsafe"
) )
var ( var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
_QueryPerformanceCounter = kernel32.NewProc("QueryPerformanceCounter") _QueryPerformanceCounter = kernel32.NewProc("QueryPerformanceCounter")
_QueryPerformanceFrequency = kernel32.NewProc("QueryPerformanceFrequency") _QueryPerformanceFrequency = kernel32.NewProc("QueryPerformanceFrequency")
) )
@@ -21,13 +23,11 @@ func init() {
// On Windows, time.Time handled in time package cannot have the nanosecond precision. // On Windows, time.Time handled in time package cannot have the nanosecond precision.
// The reason is that by default, it doesn't use QueryPerformanceCounter[1], but instead, use "interrupt time" // The reason is that by default, it doesn't use QueryPerformanceCounter[1], but instead, use "interrupt time"
// which doesn't support nanoseconds precision (though it is a monotonic) [2, 3, 4, 5]. // which doesn't support nanoseconds precision (though it is a monotonic) [2, 3].
// //
// [1] https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter // [1] https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter
// [2] https://github.com/golang/go/blob/0cd309e12818f988693bf8e4d9f1453331dcf9f2/src/runtime/sys_windows_amd64.s#L297-L298 // [2] https://github.com/golang/go/blob/go1.24.0/src/runtime/sys_windows_amd64.s#L279-L284
// [3] https://github.com/golang/go/blob/0cd309e12818f988693bf8e4d9f1453331dcf9f2/src/runtime/os_windows.go#L549-L551 // [3] https://github.com/golang/go/blob/go1.24.0/src/runtime/time_windows.h#L7-L13
// [4] https://github.com/golang/go/blob/master/src/runtime/time_windows.h#L7-L13
// [5] http://web.archive.org/web/20210411000829/https://wrkhpi.wordpress.com/2007/08/09/getting-os-information-the-kuser_shared_data-structure/
// //
// Therefore, on Windows, we directly invoke the syscall for QPC instead of time.Now or runtime.nanotime. // Therefore, on Windows, we directly invoke the syscall for QPC instead of time.Now or runtime.nanotime.
// See https://github.com/golang/go/issues/31160 for example. // See https://github.com/golang/go/issues/31160 for example.

View File

@@ -1,4 +1,4 @@
//go:build !plan9 && !js && !tinygo //go:build !(plan9 || js)
package sock package sock

View File

@@ -1,4 +1,4 @@
//go:build plan9 || js || tinygo //go:build plan9 || js
package sock package sock

View File

@@ -1,5 +1,3 @@
//go:build linux && !tinygo
package sysfs package sysfs
import ( import (

View File

@@ -1,13 +0,0 @@
//go:build tinygo
package sysfs
import (
"os"
"github.com/tetratelabs/wazero/experimental/sys"
)
func datasync(f *os.File) sys.Errno {
return sys.ENOSYS
}

View File

@@ -3,6 +3,7 @@ package sysfs
import ( import (
"io/fs" "io/fs"
"os" "os"
"path"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/platform"
@@ -63,6 +64,48 @@ func (d *dirFS) Mkdir(path string, perm fs.FileMode) (errno experimentalsys.Errn
return return
} }
// Chmod implements the same method as documented on sys.FS
func (d *dirFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno {
err := os.Chmod(d.join(path), perm)
return experimentalsys.UnwrapOSError(err)
}
// Rename implements the same method as documented on sys.FS
func (d *dirFS) Rename(from, to string) experimentalsys.Errno {
from, to = d.join(from), d.join(to)
return rename(from, to)
}
// Rmdir implements the same method as documented on sys.FS
func (d *dirFS) Rmdir(path string) experimentalsys.Errno {
return rmdir(d.join(path))
}
// Unlink implements the same method as documented on sys.FS
func (d *dirFS) Unlink(path string) (err experimentalsys.Errno) {
return unlink(d.join(path))
}
// Link implements the same method as documented on sys.FS
func (d *dirFS) Link(oldName, newName string) experimentalsys.Errno {
err := os.Link(d.join(oldName), d.join(newName))
return experimentalsys.UnwrapOSError(err)
}
// Symlink implements the same method as documented on sys.FS
func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno {
// Creating a symlink with an absolute path string fails with a "not permitted" error.
// https://github.com/WebAssembly/wasi-filesystem/blob/v0.2.0/path-resolution.md#symlinks
if path.IsAbs(oldName) {
return experimentalsys.EPERM
}
// Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved
// when dereference the `link` on its usage (e.g. readlink, read, etc).
// https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
err := os.Symlink(oldName, d.join(link))
return experimentalsys.UnwrapOSError(err)
}
// Readlink implements the same method as documented on sys.FS // Readlink implements the same method as documented on sys.FS
func (d *dirFS) Readlink(path string) (string, experimentalsys.Errno) { func (d *dirFS) Readlink(path string) (string, experimentalsys.Errno) {
// Note: do not use syscall.Readlink as that causes race on Windows. // Note: do not use syscall.Readlink as that causes race on Windows.
@@ -74,11 +117,6 @@ func (d *dirFS) Readlink(path string) (string, experimentalsys.Errno) {
return platform.ToPosixPath(dst), 0 return platform.ToPosixPath(dst), 0
} }
// Rmdir implements the same method as documented on sys.FS
func (d *dirFS) Rmdir(path string) experimentalsys.Errno {
return rmdir(d.join(path))
}
// Utimens implements the same method as documented on sys.FS // Utimens implements the same method as documented on sys.FS
func (d *dirFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno { func (d *dirFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno {
return utimens(d.join(path), atim, mtim) return utimens(d.join(path), atim, mtim)

View File

@@ -1,48 +0,0 @@
//go:build !tinygo
package sysfs
import (
"io/fs"
"os"
"path"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
)
// Link implements the same method as documented on sys.FS
func (d *dirFS) Link(oldName, newName string) experimentalsys.Errno {
err := os.Link(d.join(oldName), d.join(newName))
return experimentalsys.UnwrapOSError(err)
}
// Unlink implements the same method as documented on sys.FS
func (d *dirFS) Unlink(path string) (err experimentalsys.Errno) {
return unlink(d.join(path))
}
// Rename implements the same method as documented on sys.FS
func (d *dirFS) Rename(from, to string) experimentalsys.Errno {
from, to = d.join(from), d.join(to)
return rename(from, to)
}
// Chmod implements the same method as documented on sys.FS
func (d *dirFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno {
err := os.Chmod(d.join(path), perm)
return experimentalsys.UnwrapOSError(err)
}
// Symlink implements the same method as documented on sys.FS
func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno {
// Creating a symlink with an absolute path string fails with a "not permitted" error.
// https://github.com/WebAssembly/wasi-filesystem/blob/v0.2.0/path-resolution.md#symlinks
if path.IsAbs(oldName) {
return experimentalsys.EPERM
}
// Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved
// when dereference the `link` on its usage (e.g. readlink, read, etc).
// https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
err := os.Symlink(oldName, d.join(link))
return experimentalsys.UnwrapOSError(err)
}

View File

@@ -1,34 +0,0 @@
//go:build tinygo
package sysfs
import (
"io/fs"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
)
// Link implements the same method as documented on sys.FS
func (d *dirFS) Link(oldName, newName string) experimentalsys.Errno {
return experimentalsys.ENOSYS
}
// Unlink implements the same method as documented on sys.FS
func (d *dirFS) Unlink(path string) (err experimentalsys.Errno) {
return experimentalsys.ENOSYS
}
// Rename implements the same method as documented on sys.FS
func (d *dirFS) Rename(from, to string) experimentalsys.Errno {
return experimentalsys.ENOSYS
}
// Chmod implements the same method as documented on sys.FS
func (d *dirFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno {
return experimentalsys.ENOSYS
}
// Symlink implements the same method as documented on sys.FS
func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno {
return experimentalsys.ENOSYS
}

View File

@@ -1,4 +1,4 @@
//go:build unix && !tinygo //go:build unix
package sysfs package sysfs

View File

@@ -1,4 +1,4 @@
//go:build !(unix || windows) || tinygo //go:build !(unix || windows)
package sysfs package sysfs

View File

@@ -5,26 +5,20 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/windows"
"github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/experimental/sys"
) )
const ( const (
nonBlockingFileReadSupported = true nonBlockingFileReadSupported = true
nonBlockingFileWriteSupported = false nonBlockingFileWriteSupported = false
_ERROR_IO_INCOMPLETE = syscall.Errno(996)
) )
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
// procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe
var ( var (
// procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe kernel32 = syscall.NewLazyDLL("kernel32.dll")
// procPeekNamedPipe exposes PeekNamedPipe from kernel32.
procPeekNamedPipe = kernel32.NewProc("PeekNamedPipe") procPeekNamedPipe = kernel32.NewProc("PeekNamedPipe")
// procGetOverlappedResult is the syscall.LazyProc in kernel32 for GetOverlappedResult
procGetOverlappedResult = kernel32.NewProc("GetOverlappedResult")
// procCreateEventW is the syscall.LazyProc in kernel32 for CreateEventW
procCreateEventW = kernel32.NewProc("CreateEventW")
) )
// readFd returns ENOSYS on unsupported platforms. // readFd returns ENOSYS on unsupported platforms.
@@ -52,7 +46,7 @@ func readFd(fd uintptr, buf []byte) (int, sys.Errno) {
return un, sys.UnwrapOSError(err) return un, sys.UnwrapOSError(err)
} }
func writeFd(fd uintptr, buf []byte) (int, sys.Errno) { func writeFd(uintptr, []byte) (int, sys.Errno) {
return -1, sys.ENOSYS return -1, sys.ENOSYS
} }
@@ -60,7 +54,7 @@ func readSocket(h uintptr, buf []byte) (int, sys.Errno) {
// Poll the socket to ensure that we never perform a blocking/overlapped Read. // Poll the socket to ensure that we never perform a blocking/overlapped Read.
// //
// When the socket is closed by the remote peer, wsaPoll will return n=1 and // When the socket is closed by the remote peer, wsaPoll will return n=1 and
// errno=0, and syscall.ReadFile will return n=0 and errno=0 -- which indicates // errno=0, and windows.ReadFile will return n=0 and errno=0 -- which indicates
// io.EOF. // io.EOF.
if n, errno := wsaPoll( if n, errno := wsaPoll(
[]pollFd{newPollFd(h, _POLLIN, 0)}, 0); !errors.Is(errno, sys.Errno(0)) { []pollFd{newPollFd(h, _POLLIN, 0)}, 0); !errors.Is(errno, sys.Errno(0)) {
@@ -81,25 +75,25 @@ func readSocket(h uintptr, buf []byte) (int, sys.Errno) {
// //
// We are currently skipping checking if hFile was opened with FILE_FLAG_OVERLAPPED but using // We are currently skipping checking if hFile was opened with FILE_FLAG_OVERLAPPED but using
// both lpOverlapped and lpNumberOfBytesRead. // both lpOverlapped and lpNumberOfBytesRead.
var overlapped syscall.Overlapped var overlapped windows.Overlapped
// Create an event to wait on. // Create an event to wait on.
if hEvent, err := createEventW(nil, true, false, nil); err != 0 { if hEvent, err := windows.CreateEvent(nil, 1, 0, nil); err != nil {
return 0, sys.UnwrapOSError(err) return 0, sys.UnwrapOSError(err)
} else { } else {
overlapped.HEvent = syscall.Handle(hEvent) overlapped.HEvent = windows.Handle(hEvent)
} }
var done uint32 var done uint32
errno := syscall.ReadFile(syscall.Handle(h), buf, &done, &overlapped) errno := windows.ReadFile(windows.Handle(h), buf, &done, &overlapped)
if errors.Is(errno, syscall.ERROR_IO_PENDING) { if errors.Is(errno, windows.ERROR_IO_PENDING) {
errno = syscall.CancelIo(syscall.Handle(h)) errno = windows.CancelIo(windows.Handle(h))
if errno != nil { if errno != nil {
return 0, sys.UnwrapOSError(errno) // This is a fatal error. CancelIo failed. return 0, sys.UnwrapOSError(errno) // This is a fatal error. CancelIo failed.
} }
done, errno = getOverlappedResult(syscall.Handle(h), &overlapped, true) // wait for I/O to complete(cancel or finish). Overwrite done and errno. errno = windows.GetOverlappedResult(windows.Handle(h), &overlapped, &done, true) // wait for I/O to complete(cancel or finish). Overwrite done and errno.
if errors.Is(errno, syscall.ERROR_OPERATION_ABORTED) { if errors.Is(errno, windows.ERROR_OPERATION_ABORTED) {
return int(done), sys.EAGAIN // This is one of the expected behavior, I/O was cancelled(completed) before finished. return int(done), sys.EAGAIN // This is one of the expected behavior, I/O was cancelled(completed) before finished.
} }
} }
@@ -109,9 +103,9 @@ func readSocket(h uintptr, buf []byte) (int, sys.Errno) {
func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) { func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) {
var done uint32 var done uint32
var overlapped syscall.Overlapped var overlapped windows.Overlapped
errno := syscall.WriteFile(syscall.Handle(fd), buf, &done, &overlapped) errno := windows.WriteFile(windows.Handle(fd), buf, &done, &overlapped)
if errors.Is(errno, syscall.ERROR_IO_PENDING) { if errors.Is(errno, windows.ERROR_IO_PENDING) {
errno = syscall.EAGAIN errno = syscall.EAGAIN
} }
return int(done), sys.UnwrapOSError(errno) return int(done), sys.UnwrapOSError(errno)
@@ -137,39 +131,3 @@ func rmdir(path string) sys.Errno {
err := syscall.Rmdir(path) err := syscall.Rmdir(path)
return sys.UnwrapOSError(err) return sys.UnwrapOSError(err)
} }
func getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, wait bool) (uint32, syscall.Errno) {
var totalBytesAvail uint32
var bwait uintptr
if wait {
bwait = 0xFFFFFFFF
}
totalBytesPtr := unsafe.Pointer(&totalBytesAvail)
_, _, errno := syscall.SyscallN(
procGetOverlappedResult.Addr(),
uintptr(handle), // [in] HANDLE hFile,
uintptr(unsafe.Pointer(overlapped)), // [in] LPOVERLAPPED lpOverlapped,
uintptr(totalBytesPtr), // [out] LPDWORD lpNumberOfBytesTransferred,
bwait) // [in] BOOL bWait
return totalBytesAvail, errno
}
func createEventW(lpEventAttributes *syscall.SecurityAttributes, bManualReset bool, bInitialState bool, lpName *uint16) (uintptr, syscall.Errno) {
var manualReset uintptr
var initialState uintptr
if bManualReset {
manualReset = 1
}
if bInitialState {
initialState = 1
}
handle, _, errno := syscall.SyscallN(
procCreateEventW.Addr(),
uintptr(unsafe.Pointer(lpEventAttributes)), // [in] LPSECURITY_ATTRIBUTES lpEventAttributes,
manualReset, // [in] BOOL bManualReset,
initialState, // [in] BOOL bInitialState,
uintptr(unsafe.Pointer(lpName)), // [in, opt]LPCWSTR lpName,
)
return handle, errno
}

View File

@@ -1,21 +1,13 @@
//go:build (linux || darwin) && !tinygo //go:build linux || darwin
package sysfs package sysfs
import ( import (
"syscall" "syscall"
"unsafe"
"github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/experimental/sys"
) )
func timesToPtr(times *[2]syscall.Timespec) unsafe.Pointer { //nolint:unused
if times != nil {
return unsafe.Pointer(&times[0])
}
return unsafe.Pointer(nil)
}
func timesToTimespecs(atim int64, mtim int64) (times *[2]syscall.Timespec) { func timesToTimespecs(atim int64, mtim int64) (times *[2]syscall.Timespec) {
// When both inputs are omitted, there is nothing to change. // When both inputs are omitted, there is nothing to change.
if atim == sys.UTIME_OMIT && mtim == sys.UTIME_OMIT { if atim == sys.UTIME_OMIT && mtim == sys.UTIME_OMIT {

View File

@@ -2,28 +2,19 @@ package sysfs
import ( import (
"syscall" "syscall"
_ "unsafe" "unsafe"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
) )
const ( const _UTIME_OMIT = -2
_AT_FDCWD = -0x2
_AT_SYMLINK_NOFOLLOW = 0x0020
_UTIME_OMIT = -2
)
//go:noescape
//go:linkname utimensat syscall.utimensat
func utimensat(dirfd int, path string, times *[2]syscall.Timespec, flags int) error
func utimens(path string, atim, mtim int64) experimentalsys.Errno { func utimens(path string, atim, mtim int64) experimentalsys.Errno {
times := timesToTimespecs(atim, mtim) times := timesToTimespecs(atim, mtim)
if times == nil { if times == nil {
return 0 return 0
} }
var flags int return experimentalsys.UnwrapOSError(syscall.UtimesNano(path, times[:]))
return experimentalsys.UnwrapOSError(utimensat(_AT_FDCWD, path, times, flags))
} }
func futimens(fd uintptr, atim, mtim int64) experimentalsys.Errno { func futimens(fd uintptr, atim, mtim int64) experimentalsys.Errno {
@@ -31,7 +22,7 @@ func futimens(fd uintptr, atim, mtim int64) experimentalsys.Errno {
if times == nil { if times == nil {
return 0 return 0
} }
_p0 := timesToPtr(times) _p0 := unsafe.Pointer(&times[0])
// Warning: futimens only exists since High Sierra (10.13). // Warning: futimens only exists since High Sierra (10.13).
_, _, e1 := syscall_syscall6(libc_futimens_trampoline_addr, fd, uintptr(_p0), 0, 0, 0, 0) _, _, e1 := syscall_syscall6(libc_futimens_trampoline_addr, fd, uintptr(_p0), 0, 0, 0, 0)

View File

@@ -1,33 +1,22 @@
//go:build !tinygo
package sysfs package sysfs
import ( import (
"syscall" "syscall"
"unsafe" "unsafe"
_ "unsafe"
"golang.org/x/sys/unix"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
) )
const ( const _UTIME_OMIT = unix.UTIME_OMIT
_AT_FDCWD = -0x64
_UTIME_OMIT = (1 << 30) - 2
)
func utimens(path string, atim, mtim int64) experimentalsys.Errno { func utimens(path string, atim, mtim int64) experimentalsys.Errno {
times := timesToTimespecs(atim, mtim) times := timesToTimespecs(atim, mtim)
if times == nil { if times == nil {
return 0 return 0
} }
return experimentalsys.UnwrapOSError(syscall.UtimesNano(path, times[:]))
var flags int
var _p0 *byte
_p0, err := syscall.BytePtrFromString(path)
if err == nil {
err = utimensat(_AT_FDCWD, uintptr(unsafe.Pointer(_p0)), times, flags)
}
return experimentalsys.UnwrapOSError(err)
} }
// On linux, implement futimens via utimensat with the NUL path. // On linux, implement futimens via utimensat with the NUL path.

View File

@@ -1,4 +1,4 @@
//go:build (!windows && !linux && !darwin) || tinygo //go:build !(windows || linux || darwin)
package sysfs package sysfs

View File

@@ -1,14 +0,0 @@
//go:build tinygo
package sysfs
import (
"io/fs"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/sys"
)
func inoFromFileInfo(_ string, info fs.FileInfo) (sys.Inode, experimentalsys.Errno) {
return 0, experimentalsys.ENOTSUP
}

View File

@@ -1,4 +1,4 @@
//go:build !windows && !plan9 && !tinygo //go:build unix
package sysfs package sysfs

View File

@@ -1,3 +1,5 @@
//go:build !(unix || windows)
package sysfs package sysfs
import ( import (

View File

@@ -1,4 +1,4 @@
//go:build !windows && !plan9 && !tinygo //go:build unix
package sysfs package sysfs

View File

@@ -1,4 +1,4 @@
//go:build plan9 || tinygo //go:build !(unix || windows)
package sysfs package sysfs

View File

@@ -1,3 +1,5 @@
//go:build freebsd || dragonfly
package sysfs package sysfs
import ( import (

View File

@@ -1,31 +0,0 @@
//go:build illumos || solaris
package sysfs
import (
"syscall"
"github.com/tetratelabs/wazero/experimental/sys"
)
const supportedSyscallOflag = sys.O_DIRECTORY | sys.O_DSYNC | sys.O_NOFOLLOW | sys.O_NONBLOCK | sys.O_RSYNC
func withSyscallOflag(oflag sys.Oflag, flag int) int {
if oflag&sys.O_DIRECTORY != 0 {
// See https://github.com/illumos/illumos-gate/blob/edd580643f2cf1434e252cd7779e83182ea84945/usr/src/uts/common/sys/fcntl.h#L90
flag |= 0x1000000
}
if oflag&sys.O_DSYNC != 0 {
flag |= syscall.O_DSYNC
}
if oflag&sys.O_NOFOLLOW != 0 {
flag |= syscall.O_NOFOLLOW
}
if oflag&sys.O_NONBLOCK != 0 {
flag |= syscall.O_NONBLOCK
}
if oflag&sys.O_RSYNC != 0 {
flag |= syscall.O_RSYNC
}
return flag
}

View File

@@ -1,4 +1,4 @@
//go:build !tinygo //go:build linux || netbsd || openbsd || solaris
package sysfs package sysfs

View File

@@ -1,25 +0,0 @@
//go:build tinygo
package sysfs
import (
"io/fs"
"os"
"github.com/tetratelabs/wazero/experimental/sys"
)
const supportedSyscallOflag = sys.Oflag(0)
func withSyscallOflag(oflag sys.Oflag, flag int) int {
// O_DIRECTORY not defined
// O_DSYNC not defined
// O_NOFOLLOW not defined
// O_NONBLOCK not defined
// O_RSYNC not defined
return flag
}
func openFile(path string, oflag sys.Oflag, perm fs.FileMode) (*os.File, sys.Errno) {
return nil, sys.ENOSYS
}

View File

@@ -1,4 +1,4 @@
//go:build !windows && !tinygo //go:build !windows
package sysfs package sysfs

View File

@@ -1,10 +1,8 @@
//go:build !darwin && !linux && !windows && !illumos && !solaris && !freebsd //go:build !(freebsd || dragonfly || darwin || linux || netbsd || openbsd || solaris || windows)
package sysfs package sysfs
import ( import "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/experimental/sys"
)
const supportedSyscallOflag = sys.Oflag(0) const supportedSyscallOflag = sys.Oflag(0)

View File

@@ -57,7 +57,7 @@ func openFile(path string, oflag sys.Oflag, perm fs.FileMode) (*os.File, sys.Err
const supportedSyscallOflag = sys.O_NONBLOCK const supportedSyscallOflag = sys.O_NONBLOCK
// Map to synthetic values here https://github.com/golang/go/blob/go1.20/src/syscall/types_windows.go#L34-L48 // Map to synthetic values here https://github.com/golang/go/blob/go1.24.0/src/syscall/types_windows.go#L35-L52
func withSyscallOflag(oflag sys.Oflag, flag int) int { func withSyscallOflag(oflag sys.Oflag, flag int) int {
// O_DIRECTORY not defined in windows // O_DIRECTORY not defined in windows
// O_DSYNC not defined in windows // O_DSYNC not defined in windows

View File

@@ -89,7 +89,7 @@ func (f *osFile) SetAppend(enable bool) (errno experimentalsys.Errno) {
} }
// appendMode cannot be changed later, so we have to re-open the file // appendMode cannot be changed later, so we have to re-open the file
// https://github.com/golang/go/blob/go1.23/src/os/file_unix.go#L60 // https://github.com/golang/go/blob/go1.24.0/src/os/file_unix.go#L65
return fileError(f, f.closed, f.reopen()) return fileError(f, f.closed, f.reopen())
} }

View File

@@ -1,4 +1,4 @@
//go:build windows || (linux && !tinygo) || darwin //go:build windows || linux || darwin
package sysfs package sysfs

View File

@@ -1,5 +1,3 @@
//go:build !tinygo
package sysfs package sysfs
import ( import (

View File

@@ -1,4 +1,4 @@
//go:build !(linux || darwin || windows) || tinygo //go:build !(linux || darwin || windows)
package sysfs package sysfs

View File

@@ -1,4 +1,4 @@
//go:build !windows && !plan9 && !tinygo //go:build unix
package sysfs package sysfs

View File

@@ -1,3 +1,5 @@
//go:build !(unix || windows)
package sysfs package sysfs
import ( import (

View File

@@ -1,4 +1,4 @@
//go:build (linux || darwin || windows) && !tinygo //go:build linux || darwin || windows
package sysfs package sysfs

View File

@@ -1,4 +1,4 @@
//go:build (linux || darwin) && !tinygo //go:build linux || darwin
package sysfs package sysfs

View File

@@ -1,4 +1,4 @@
//go:build (!linux && !darwin && !windows) || tinygo //go:build !(linux || darwin || windows)
package sysfs package sysfs

View File

@@ -7,6 +7,8 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/windows"
"github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/fsapi"
socketapi "github.com/tetratelabs/wazero/internal/sock" socketapi "github.com/tetratelabs/wazero/internal/sock"
@@ -14,8 +16,7 @@ import (
const ( const (
// MSG_PEEK is the flag PEEK for syscall.Recvfrom on Windows. // MSG_PEEK is the flag PEEK for syscall.Recvfrom on Windows.
// This constant is not exported on this platform. MSG_PEEK = windows.MSG_PEEK
MSG_PEEK = 0x2
// _FIONBIO is the flag to set the O_NONBLOCK flag on socket handles using ioctlsocket. // _FIONBIO is the flag to set the O_NONBLOCK flag on socket handles using ioctlsocket.
_FIONBIO = 0x8004667e _FIONBIO = 0x8004667e
) )

View File

@@ -1,37 +0,0 @@
//go:build (amd64 || arm64) && (darwin || freebsd)
package sysfs
import (
"io/fs"
"os"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/sys"
)
// dirNlinkIncludesDot is true because even though os.File filters out dot
// entries, the underlying syscall.Stat includes them.
//
// Note: this is only used in tests
const dirNlinkIncludesDot = true
func lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
if info, err := os.Lstat(path); err != nil {
return sys.Stat_t{}, experimentalsys.UnwrapOSError(err)
} else {
return sys.NewStat_t(info), 0
}
}
func stat(path string) (sys.Stat_t, experimentalsys.Errno) {
if info, err := os.Stat(path); err != nil {
return sys.Stat_t{}, experimentalsys.UnwrapOSError(err)
} else {
return sys.NewStat_t(info), 0
}
}
func statFile(f fs.File) (sys.Stat_t, experimentalsys.Errno) {
return defaultStatFile(f)
}

View File

@@ -1,4 +1,4 @@
//go:build (amd64 || arm64 || ppc64le || riscv64 || s390x) && linux //go:build linux || darwin || freebsd || netbsd || openbsd || dragonfly || solaris
// Note: This expression is not the same as compiler support, even if it looks // Note: This expression is not the same as compiler support, even if it looks
// similar. Platform functions here are used in interpreter mode as well. // similar. Platform functions here are used in interpreter mode as well.

View File

@@ -1,4 +1,4 @@
//go:build (!((amd64 || arm64 || ppc64le || riscv64 || s390x) && linux) && !((amd64 || arm64) && (darwin || freebsd)) && !((amd64 || arm64) && windows)) || js //go:build !(linux || darwin || freebsd || netbsd || openbsd || dragonfly || solaris || windows)
package sysfs package sysfs

View File

@@ -1,5 +1,3 @@
//go:build (amd64 || arm64) && windows
package sysfs package sysfs
import ( import (

View File

@@ -1,4 +1,4 @@
//go:build !windows && !plan9 && !tinygo //go:build unix
package sysfs package sysfs

View File

@@ -1,12 +1,14 @@
//go:build !(unix || windows)
package sysfs package sysfs
import ( import (
"syscall" "os"
"github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/experimental/sys"
) )
func unlink(name string) sys.Errno { func unlink(name string) sys.Errno {
err := syscall.Remove(name) err := os.Remove(name)
return sys.UnwrapOSError(err) return sys.UnwrapOSError(err)
} }

View File

@@ -38,6 +38,7 @@ func DecodeModule(
memSizer := newMemorySizer(memoryLimitPages, memoryCapacityFromMax) memSizer := newMemorySizer(memoryLimitPages, memoryCapacityFromMax)
m := &wasm.Module{} m := &wasm.Module{}
var lastSectionID wasm.SectionID
var info, line, str, abbrev, ranges []byte // For DWARF Data. var info, line, str, abbrev, ranges []byte // For DWARF Data.
for { for {
// TODO: except custom sections, all others are required to be in order, but we aren't checking yet. // TODO: except custom sections, all others are required to be in order, but we aren't checking yet.
@@ -54,6 +55,12 @@ func DecodeModule(
return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err) return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err)
} }
var ok bool
lastSectionID, ok = checkSectionOrder(sectionID, lastSectionID)
if !ok {
return nil, errors.New("invalid section order")
}
sectionContentStart := r.Len() sectionContentStart := r.Len()
switch sectionID { switch sectionID {
case wasm.SectionIDCustom: case wasm.SectionIDCustom:
@@ -123,9 +130,6 @@ func DecodeModule(
case wasm.SectionIDExport: case wasm.SectionIDExport:
m.ExportSection, m.Exports, err = decodeExportSection(r) m.ExportSection, m.Exports, err = decodeExportSection(r)
case wasm.SectionIDStart: case wasm.SectionIDStart:
if m.StartSection != nil {
return nil, errors.New("multiple start sections are invalid")
}
m.StartSection, err = decodeStartSection(r) m.StartSection, err = decodeStartSection(r)
case wasm.SectionIDElement: case wasm.SectionIDElement:
m.ElementSection, err = decodeElementSection(r, enabledFeatures) m.ElementSection, err = decodeElementSection(r, enabledFeatures)
@@ -164,6 +168,34 @@ func DecodeModule(
return m, nil return m, nil
} }
func checkSectionOrder(current, previous wasm.SectionID) (byte, bool) {
// https://webassembly.github.io/spec/core/binary/modules.html#binary-module
// Custom sections can show up anywhere.
if current == wasm.SectionIDCustom {
return previous, true
}
// DataCount was introduced in Wasm 2.0,
// and it's the maximum we support so far.
// It must come after Element and before Code.
if current > wasm.SectionIDDataCount {
return current, false
}
if current == wasm.SectionIDDataCount {
return current, previous <= wasm.SectionIDElement
}
if previous == wasm.SectionIDDataCount {
return current, current >= wasm.SectionIDCode
}
// Tag will be introduced in Wasm 3.0.
// It must come after Memory and before Global.
// Otherwise, strictly increasing order.
return current, current > previous
}
// memorySizer derives min, capacity and max pages from decoded wasm. // memorySizer derives min, capacity and max pages from decoded wasm.
type memorySizer func(minPages uint32, maxPages *uint32) (min uint32, capacity uint32, max uint32) type memorySizer func(minPages uint32, maxPages *uint32) (min uint32, capacity uint32, max uint32)

View File

@@ -40,6 +40,11 @@ func readMemArg(pc uint64, body []byte) (align, offset uint32, read uint64, err
err = fmt.Errorf("read memory align: %v", err) err = fmt.Errorf("read memory align: %v", err)
return return
} }
if align >= 32 {
// Prevent 1<<align uint32 overflow.
err = fmt.Errorf("invalid memory alignment")
return
}
read += num read += num
offset, num, err = leb128.LoadUint32(body[pc+num:]) offset, num, err = leb128.LoadUint32(body[pc+num:])

View File

@@ -87,8 +87,7 @@ func NewMemoryInstance(memSec *Memory, allocator experimental.MemoryAllocator, m
// //
// Also, allocating Max here isn't harmful as the Go runtime uses mmap for large allocations, therefore, // Also, allocating Max here isn't harmful as the Go runtime uses mmap for large allocations, therefore,
// the memory buffer allocation here is virtual and doesn't consume physical memory until it's used. // the memory buffer allocation here is virtual and doesn't consume physical memory until it's used.
// * https://github.com/golang/go/blob/8121604559035734c9677d5281bbdac8b1c17a1e/src/runtime/malloc.go#L1059 // * https://github.com/golang/go/blob/go1.24.0/src/runtime/malloc.go#L1059
// * https://github.com/golang/go/blob/8121604559035734c9677d5281bbdac8b1c17a1e/src/runtime/malloc.go#L1165
buffer = make([]byte, minBytes, maxBytes) buffer = make([]byte, minBytes, maxBytes)
} else { } else {
buffer = make([]byte, minBytes, capBytes) buffer = make([]byte, minBytes, capBytes)

View File

@@ -456,7 +456,7 @@ func (m *ModuleInstance) resolveImports(ctx context.Context, module *Module) (er
return return
} }
if expected.Min > importedTable.Min { if uint64(expected.Min) > uint64(len(importedTable.References)) {
err = errorMinSizeMismatch(i, expected.Min, importedTable.Min) err = errorMinSizeMismatch(i, expected.Min, importedTable.Min)
return return
} }

View File

@@ -1,4 +1,4 @@
//go:build (amd64 || arm64) && (darwin || freebsd) //go:build darwin || freebsd || netbsd
package sys package sys
@@ -13,16 +13,16 @@ func statFromFileInfo(info fs.FileInfo) Stat_t {
if d, ok := info.Sys().(*syscall.Stat_t); ok { if d, ok := info.Sys().(*syscall.Stat_t); ok {
st := Stat_t{} st := Stat_t{}
st.Dev = uint64(d.Dev) st.Dev = uint64(d.Dev)
st.Ino = d.Ino st.Ino = Inode(d.Ino)
st.Mode = info.Mode() st.Mode = info.Mode()
st.Nlink = uint64(d.Nlink) st.Nlink = uint64(d.Nlink)
st.Size = d.Size st.Size = int64(d.Size)
atime := d.Atimespec atime := d.Atimespec
st.Atim = atime.Sec*1e9 + atime.Nsec st.Atim = EpochNanos(atime.Sec)*1e9 + EpochNanos(atime.Nsec)
mtime := d.Mtimespec mtime := d.Mtimespec
st.Mtim = mtime.Sec*1e9 + mtime.Nsec st.Mtim = EpochNanos(mtime.Sec)*1e9 + EpochNanos(mtime.Nsec)
ctime := d.Ctimespec ctime := d.Ctimespec
st.Ctim = ctime.Sec*1e9 + ctime.Nsec st.Ctim = EpochNanos(ctime.Sec)*1e9 + EpochNanos(ctime.Nsec)
return st return st
} }
return defaultStatFromFileInfo(info) return defaultStatFromFileInfo(info)

View File

@@ -1,32 +0,0 @@
//go:build (amd64 || arm64 || ppc64le || riscv64 || s390x) && linux
// Note: This expression is not the same as compiler support, even if it looks
// similar. Platform functions here are used in interpreter mode as well.
package sys
import (
"io/fs"
"syscall"
)
const sysParseable = true
func statFromFileInfo(info fs.FileInfo) Stat_t {
if d, ok := info.Sys().(*syscall.Stat_t); ok {
st := Stat_t{}
st.Dev = uint64(d.Dev)
st.Ino = uint64(d.Ino)
st.Mode = info.Mode()
st.Nlink = uint64(d.Nlink)
st.Size = d.Size
atime := d.Atim
st.Atim = atime.Sec*1e9 + atime.Nsec
mtime := d.Mtim
st.Mtim = mtime.Sec*1e9 + mtime.Nsec
ctime := d.Ctim
st.Ctim = ctime.Sec*1e9 + ctime.Nsec
return st
}
return defaultStatFromFileInfo(info)
}

29
vendor/github.com/tetratelabs/wazero/sys/stat_posix.go generated vendored Normal file
View File

@@ -0,0 +1,29 @@
//go:build linux || openbsd || dragonfly || solaris
package sys
import (
"io/fs"
"syscall"
)
const sysParseable = true
func statFromFileInfo(info fs.FileInfo) Stat_t {
if d, ok := info.Sys().(*syscall.Stat_t); ok {
st := Stat_t{}
st.Dev = uint64(d.Dev)
st.Ino = Inode(d.Ino)
st.Mode = info.Mode()
st.Nlink = uint64(d.Nlink)
st.Size = int64(d.Size)
atime := d.Atim
st.Atim = EpochNanos(atime.Sec)*1e9 + EpochNanos(atime.Nsec)
mtime := d.Mtim
st.Mtim = EpochNanos(mtime.Sec)*1e9 + EpochNanos(mtime.Nsec)
ctime := d.Ctim
st.Ctim = EpochNanos(ctime.Sec)*1e9 + EpochNanos(ctime.Nsec)
return st
}
return defaultStatFromFileInfo(info)
}

View File

@@ -1,4 +1,4 @@
//go:build (!((amd64 || arm64 || ppc64le || riscv64 || s390x) && linux) && !((amd64 || arm64) && (darwin || freebsd)) && !((amd64 || arm64) && windows)) || js //go:build !(linux || darwin || freebsd || netbsd || openbsd || dragonfly || solaris || windows)
package sys package sys

View File

@@ -1,5 +1,3 @@
//go:build (amd64 || arm64) && windows
package sys package sys
import ( import (

4
vendor/modules.txt vendored
View File

@@ -1326,8 +1326,8 @@ github.com/stretchr/testify/assert
github.com/stretchr/testify/assert/yaml github.com/stretchr/testify/assert/yaml
github.com/stretchr/testify/require github.com/stretchr/testify/require
github.com/stretchr/testify/suite github.com/stretchr/testify/suite
# github.com/tetratelabs/wazero v1.10.1 # github.com/tetratelabs/wazero v1.11.0
## explicit; go 1.23.0 ## explicit; go 1.24.0
github.com/tetratelabs/wazero github.com/tetratelabs/wazero
github.com/tetratelabs/wazero/api github.com/tetratelabs/wazero/api
github.com/tetratelabs/wazero/experimental github.com/tetratelabs/wazero/experimental