diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index bc3df268..ce71ab2f 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -75,12 +75,18 @@ jobs: zig: needs: build_zig_test_binary - name: Zig (${{ matrix.os }}) + name: Zig (${{ matrix.os }}, ${{ matrix.arch }}, ${{ matrix.compiler }}-compiler) runs-on: ${{ matrix.os }} strategy: fail-fast: false # don't fail fast as sometimes failures are arch/OS specific matrix: - os: [ubuntu-22.04, macos-12, windows-2022] + os: [ubuntu-22.04, macos-12, windows-2022] + compiler: [baseline] + arch: [amd64] + include: + - os: ubuntu-22.04 + compiler: optimizing + arch: "arm64" steps: - name: Checkout wazero @@ -101,15 +107,41 @@ jobs: with: go-version: ${{ env.GO_VERSION }} - - name: Install wazero - run: go install ./cmd/wazero + - name: Build wazero + run: go build -o ./wazerocli ./cmd/wazero + env: + GOARCH: ${{ matrix.arch }} + + - name: Set up QEMU + if: ${{ matrix.compiler == 'optimizing' && matrix.arch == 'arm64' }} + uses: docker/setup-qemu-action@v2 + with: # Avoid docker.io rate-limits; built with internal-images.yml + image: ghcr.io/tetratelabs/wazero/internal-binfmt + platforms: ${{ matrix.arch }} - # This runs the previously compiled Zig tests with wazero. If you need - # to troubleshoot one, you can add "-hostlogging=filesystem" after - # adding filter argument to the "Build Stdlib test binary" step. + - name: Build scratch container + if: ${{ matrix.compiler == 'optimizing' }} + run: | + echo 'FROM scratch' >> Dockerfile + echo 'CMD ["/test"]' >> Dockerfile + docker buildx build -t wazero:test --platform linux/${{ matrix.arch }} . + + + # The following two steps run the previously compiled Zig tests with wazero. + # If you need to troubleshoot any of the test, you can add "-hostlogging=filesystem" after + # adding filter argument either to step "Run built test binaries" or + # "Run built test binaries (container)". # e.g. --test-filter "Dir.Iterator but dir is deleted during iteration" - - name: Run the test binary with wazero CLI - run: wazero run -mount=:/ ./zigbin/test.wasm + + - name: Run built test binaries (container) + if: ${{ matrix.compiler == 'optimizing' }} + run: | + docker run --platform linux/${{ matrix.arch }} -v $(pwd)/zigbin:/test -v $(pwd)/wazerocli:/wazero -e WAZEROCLI=/wazero --tmpfs /tmp --rm -t wazero:test \ + /wazero run -optimizing-compiler -mount=:/ ./test/test.wasm + + - name: Run built test binaries + if: ${{ matrix.compiler != 'optimizing' }} + run: ./wazerocli run -mount=:/ ./zigbin/test.wasm build_tinygo_test_binary: name: Build TinyGo test binary @@ -194,12 +226,18 @@ jobs: tinygo: needs: build_tinygo_test_binary - name: TinyGo (${{ matrix.os }}) + name: TinyGo (${{ matrix.os }}, ${{ matrix.arch }}, ${{ matrix.compiler }}-compiler) runs-on: ${{ matrix.os }} strategy: fail-fast: false # don't fail fast as sometimes failures are arch/OS specific matrix: - os: [ubuntu-22.04, macos-12, windows-2022] + os: [ubuntu-22.04, macos-12, windows-2022] + compiler: [baseline] + arch: [amd64] + include: + - os: ubuntu-22.04 + compiler: optimizing + arch: "arm64" steps: - name: Checkout wazero @@ -220,19 +258,48 @@ jobs: with: go-version: ${{ env.GO_VERSION }} - - name: Install wazero - run: go install ./cmd/wazero + - name: Build wazero + run: go build -o ~/wazerocli ./cmd/wazero + env: + GOARCH: ${{ matrix.arch }} + + - name: Set up QEMU + if: ${{ matrix.compiler == 'optimizing' && matrix.arch == 'arm64' }} + uses: docker/setup-qemu-action@v2 + with: # Avoid docker.io rate-limits; built with internal-images.yml + image: ghcr.io/tetratelabs/wazero/internal-binfmt + platforms: ${{ matrix.arch }} - # This runs the previously compiled TinyGo tests with wazero. If you need + - name: Build scratch container + if: ${{ matrix.compiler == 'optimizing' }} + run: | + echo 'FROM scratch' >> Dockerfile + echo 'CMD ["/test"]' >> Dockerfile + docker buildx build -t wazero:test --platform linux/${{ matrix.arch }} . + + # The following steps run the previously compiled TinyGo tests with wazero. If you need # to troubleshoot one, you can add "-hostlogging=filesystem" and also a # trailing argument narrowing which test to execute. # e.g. "-test.run '^TestStatBadDir$'" - - name: Run standard library tests + + - name: Run test binaries (container) + if: ${{ matrix.compiler == 'optimizing' }} + # This runs all tests compiled above in sequence. Note: This mounts /tmp to allow t.TempDir() in tests. + run: | + cd ./tinygobin + for bin in *.test; do + echo $bin + docker run --platform linux/${{ matrix.arch }} -v $(pwd):/test -v ~/wazerocli:/wazero -e WAZEROCLI=/wazero --tmpfs /tmp --rm -t wazero:test \ + /wazero run -optimizing-compiler -mount=:/ /test/$bin -- -test.v + done + + - name: Run test binaries + if: ${{ matrix.compiler != 'optimizing' }} run: | cd ./tinygobin for bin in *.test; do echo $bin - wazero run -mount=:/ -mount=:/tmp $bin -- -test.v + ~/wazerocli run -mount=:/ -mount=:/tmp $bin -- -test.v done wasi-testsuite: @@ -325,15 +392,21 @@ jobs: go_tests: # Due to the embedding of the GOROOT of the building env(https://github.com/golang/go/blob/3c59639b902fada0a2e5a6a35bafd10fc9183b89/src/os/os_test.go#L112), # we have to build and cache on each OS unlike others in this file. - name: Go (${{ matrix.os }}, Go-${{ matrix.go-version }}) + name: Go (${{ matrix.os }}, Go-${{ matrix.go-version }}, ${{ matrix.arch }}, ${{ matrix.compiler }}-compiler) runs-on: ${{ matrix.os }} strategy: fail-fast: false # don't fail fast as sometimes failures are arch/OS specific matrix: - os: [ubuntu-22.04, macos-12, windows-2022] + os: [ubuntu-22.04, macos-12, windows-2022] + compiler: [baseline] + arch: [amd64] go-version: - "1.21" # Current Go version && The only version that supports wasip1. - + include: + - os: ubuntu-22.04 + compiler: optimizing + arch: "arm64" + go-version: "1.21" steps: - id: setup-go uses: actions/setup-go@v4 @@ -425,11 +498,44 @@ jobs: - name: Checkout wazero uses: actions/checkout@v3 - - name: Install wazero - run: go install ./cmd/wazero + - name: Build wazero + run: go build -o ~/wazerocli ./cmd/wazero + env: + GOARCH: ${{ matrix.arch }} + + - name: Set up QEMU + if: ${{ matrix.compiler == 'optimizing' && matrix.arch == 'arm64' }} + uses: docker/setup-qemu-action@v2 + with: # Avoid docker.io rate-limits; built with internal-images.yml + image: ghcr.io/tetratelabs/wazero/internal-binfmt + platforms: ${{ matrix.arch }} + + - name: Build scratch container + if: ${{ matrix.compiler == 'optimizing' }} + run: | + echo 'FROM scratch' >> Dockerfile + echo 'CMD ["/test"]' >> Dockerfile + docker buildx build -t wazero:test --platform linux/${{ matrix.arch }} . + + - if: ${{ matrix.compiler == 'optimizing' }} + name: Run test binaries (container) + run: | + echo "Running $(find ~/tests -name *.test | wc -l) test binaries" + + # Skip tests that are hideously slow (mostly compilation time) for running inside QEMU. + skip_targets="src_encoding_json|src_runtime|src_os_exec|src_math_big|src_encoding_gob|src_time|src_archive_tar|src_math_rand|src_expvar|src_testing|src_os" + # Go tests often look for files relative to the source. Change to the corresponding directory. + for bin in $(find ~/tests -name "*.test" | grep -vE "$skip_targets"); do + dir=$(basename $bin); dir=${dir%.test}; dir=${dir//_/\/} + pushd $(go env GOROOT)/$dir + # Mount / as /ROOT in docker and then remount /ROOT as / in wazero; $bin is now in /ROOT/$bin. + docker run --platform linux/arm64 -v /:/ROOT -v ~/wazerocli:/wazero -e WAZEROCLI=/wazero --tmpfs /tmp --rm -t wazero:test \ + /wazero run -optimizing-compiler -mount=/ROOT:/ -mount=/tmp:/tmp -env PWD=$PWD /ROOT/$bin -- -test.short -test.v + popd + done - - if: ${{ runner.os != 'Windows' }} - name: Run standard library tests + - if: ${{ matrix.compiler != 'optimizing' && runner.os != 'Windows' }} + name: Run test binaries run: | echo "Running $(find ~/tests -name *.test | wc -l) test binaries" @@ -437,12 +543,12 @@ jobs: for bin in ~/tests/*.test; do dir=$(basename $bin); dir=${dir%.test}; dir=${dir//_/\/} pushd $(go env GOROOT)/$dir - wazero run -mount=/:/ -env PWD=$PWD $bin -- -test.short -test.v + ~/wazerocli run -mount=/:/ -env PWD=$PWD $bin -- -test.short -test.v popd done - if: ${{ runner.os == 'Windows' }} - name: Run standard library tests + name: Run test binaries (Windows) # Ack failures on Windows. https://github.com/tetratelabs/wazero/issues/1410 continue-on-error: true run: | @@ -471,7 +577,7 @@ jobs: # Create a script with all the tests (do not run yet). echo ${MOUNT} ${IN_WASM_PWD} $GOROOT/$dir - COMMAND="wazero run -mount=${MOUNT} ${EXTRAPARAMS} -hostlogging=filesystem -env PWD=${IN_WASM_PWD} -env GOROOT=${GOROOT} -env GOOS=wasip1 $bin -- -test.short -test.v" + COMMAND="~/wazerocli run -mount=${MOUNT} ${EXTRAPARAMS} -hostlogging=filesystem -env PWD=${IN_WASM_PWD} -env GOROOT=${GOROOT} -env GOOS=wasip1 $bin -- -test.short -test.v" echo $COMMAND >> $SCRIPT # Uncomment the following line for early exit on error on Windows. # Otherwise the tests will report are successful evne on failure. diff --git a/Makefile b/Makefile index 671c1e21..90ae6acd 100644 --- a/Makefile +++ b/Makefile @@ -278,6 +278,7 @@ clean: ## Ensure a clean build fuzz_timeout_seconds ?= 10 .PHONY: fuzz fuzz: + @cd internal/integration_test/fuzz && cargo test @cd internal/integration_test/fuzz && cargo fuzz run no_diff --sanitizer=none -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds) @cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff --sanitizer=none -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds) @cd internal/integration_test/fuzz && cargo fuzz run validation --sanitizer=none -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds) diff --git a/api/wasm.go b/api/wasm.go index f8d5c387..373a3668 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -374,6 +374,7 @@ type Function interface { // Call is not goroutine-safe, therefore it is recommended to create // another Function if you want to invoke the same function concurrently. // On the other hand, sequential invocations of Call is allowed. + // However, this should not be called multiple times until the previous Call returns. // // To safely encode/decode params/results expressed as uint64, users are encouraged to // use api.EncodeXXX or DecodeXXX functions. See the docs on api.ValueType. diff --git a/cmd/wazero/wazero.go b/cmd/wazero/wazero.go index 094a350f..8412ff94 100644 --- a/cmd/wazero/wazero.go +++ b/cmd/wazero/wazero.go @@ -20,6 +20,7 @@ import ( "github.com/wasilibs/wazerox/experimental" "github.com/wasilibs/wazerox/experimental/gojs" "github.com/wasilibs/wazerox/experimental/logging" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/experimental/sock" "github.com/wasilibs/wazerox/experimental/sysfs" "github.com/wasilibs/wazerox/imports/wasi_snapshot_preview1" @@ -161,6 +162,10 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int { flags.BoolVar(&useInterpreter, "interpreter", false, "Interprets WebAssembly modules instead of compiling them into native code.") + var useOptimizingCompiler bool + flags.BoolVar(&useOptimizingCompiler, "optimizing-compiler", false, + "[Experimental] Compiles WebAssembly modules using the optimizing compiler.") + var envs sliceFlag flags.Var(&envs, "env", "key=value pair of environment variable to expose to the binary. "+ "Can be specified multiple times.") @@ -269,6 +274,8 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int { var rtc wazero.RuntimeConfig if useInterpreter { rtc = wazero.NewRuntimeConfigInterpreter() + } else if useOptimizingCompiler { + rtc = opt.NewRuntimeConfigOptimizingCompiler() } else { rtc = wazero.NewRuntimeConfig() } diff --git a/config.go b/config.go index c4074d8e..9466ab12 100644 --- a/config.go +++ b/config.go @@ -14,6 +14,7 @@ import ( experimentalsys "github.com/wasilibs/wazerox/experimental/sys" "github.com/wasilibs/wazerox/internal/engine/compiler" "github.com/wasilibs/wazerox/internal/engine/interpreter" + "github.com/wasilibs/wazerox/internal/engine/wazevo" "github.com/wasilibs/wazerox/internal/filecache" "github.com/wasilibs/wazerox/internal/internalapi" "github.com/wasilibs/wazerox/internal/platform" @@ -135,7 +136,7 @@ type RuntimeConfig interface { // However, if you use this in tests of a package not named as `main`, then wazero cannot obtain the correct // version of wazero due to the known issue of debug.BuildInfo function: https://github.com/golang/go/issues/33976. // As a consequence, your cache won't contain the correct version information and always be treated as `dev` version. - // To avoid this issue, you can pass -ldflags "-X github.com/tetratelabs/wazero/internal/version.version=foo" when running tests. + // To avoid this issue, you can pass -ldflags "-X github.com/wasilibs/wazerox/internal/version.version=foo" when running tests. WithCompilationCache(CompilationCache) RuntimeConfig // WithCustomSections toggles parsing of "custom sections". Defaults to false. @@ -190,6 +191,11 @@ type runtimeConfig struct { ensureTermination bool } +// EnableOptimizingCompiler implements experimental/opt/enabler.EnableOptimizingCompiler. +func (c *runtimeConfig) EnableOptimizingCompiler() { + c.newEngine = wazevo.NewEngine +} + // engineLessConfig helps avoid copy/pasting the wrong defaults. var engineLessConfig = &runtimeConfig{ enabledFeatures: api.CoreFeaturesV2, diff --git a/experimental/gojs/README.md b/experimental/gojs/README.md index 0cab3a4c..c8271a16 100644 --- a/experimental/gojs/README.md +++ b/experimental/gojs/README.md @@ -3,7 +3,7 @@ When `GOOS=js` and `GOARCH=wasm`, Go's compiler targets WebAssembly Binary format (%.wasm). -Wazero's "github.com/tetratelabs/wazero/experimental/gojs" package allows you to run +Wazero's "github.com/wasilibs/wazerox/experimental/gojs" package allows you to run a `%.wasm` file compiled by Go. This is similar to what is implemented in [wasm_exec.js][1]. See https://wazero.io/languages/go/ for more. diff --git a/experimental/opt/opt.go b/experimental/opt/opt.go new file mode 100644 index 00000000..be23778c --- /dev/null +++ b/experimental/opt/opt.go @@ -0,0 +1,23 @@ +package opt + +import ( + "runtime" + + wazero "github.com/wasilibs/wazerox" +) + +type enabler interface { + // EnableOptimizingCompiler enables the optimizing compiler. + // This is only implemented the internal type of wazero.runtimeConfig. + EnableOptimizingCompiler() +} + +// NewRuntimeConfigOptimizingCompiler returns a new RuntimeConfig with the optimizing compiler enabled. +func NewRuntimeConfigOptimizingCompiler() wazero.RuntimeConfig { + if runtime.GOARCH != "arm64" { + panic("UseOptimizingCompiler is only supported on arm64") + } + c := wazero.NewRuntimeConfig() + c.(enabler).EnableOptimizingCompiler() + return c +} diff --git a/experimental/opt/opt_test.go b/experimental/opt/opt_test.go new file mode 100644 index 00000000..98f213c1 --- /dev/null +++ b/experimental/opt/opt_test.go @@ -0,0 +1,20 @@ +package opt_test + +import ( + "context" + "runtime" + "testing" + + wazero "github.com/wasilibs/wazerox" + "github.com/wasilibs/wazerox/experimental/opt" + "github.com/wasilibs/wazerox/internal/testing/require" +) + +func TestUseOptimizingCompiler(t *testing.T) { + if runtime.GOARCH != "arm64" { + return + } + c := opt.NewRuntimeConfigOptimizingCompiler() + r := wazero.NewRuntimeWithConfig(context.Background(), c) + require.NoError(t, r.Close(context.Background())) +} diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go index 1c90c744..1f4526ea 100644 --- a/imports/wasi_snapshot_preview1/fs.go +++ b/imports/wasi_snapshot_preview1/fs.go @@ -464,7 +464,7 @@ var fdFilestatSetSize = newHostFunc(wasip1.FdFilestatSetSizeName, fdFilestatSetS func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { fd := int32(params[0]) - size := uint32(params[1]) + size := int64(params[1]) fsc := mod.(*wasm.ModuleInstance).Sys.FS() @@ -472,7 +472,7 @@ func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) exp if f, ok := fsc.LookupFile(fd); !ok { return experimentalsys.EBADF } else { - return f.File.Truncate(int64(size)) + return f.File.Truncate(size) } } diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go index 2c34f0e0..a2de9f92 100644 --- a/imports/wasi_snapshot_preview1/fs_test.go +++ b/imports/wasi_snapshot_preview1/fs_test.go @@ -480,6 +480,42 @@ func Test_fdFdstatGet_StdioNonblock(t *testing.T) { } } +func Test_fdFdstatSetFlagsWithTrunc(t *testing.T) { + tmpDir := t.TempDir() + fileName := "test" + + mod, r, log := requireProxyModule(t, wazero.NewModuleConfig(). + WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "/"))) + defer r.Close(testCtx) + + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + preopen := fsc.RootFS() + + fd, errno := fsc.OpenFile(preopen, fileName, experimentalsys.O_RDWR|experimentalsys.O_CREAT|experimentalsys.O_EXCL|experimentalsys.O_TRUNC, 0o600) + require.EqualErrno(t, 0, errno) + + // Write the initial text to the file. + f, ok := fsc.LookupFile(fd) + require.True(t, ok) + n, _ := f.File.Write([]byte("abc")) + require.Equal(t, n, 3) + + buf, err := os.ReadFile(joinPath(tmpDir, fileName)) + require.NoError(t, err) + require.Equal(t, "abc", string(buf)) + + requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(0)) + require.Equal(t, ` +==> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=4,flags=) +<== errno=ESUCCESS +`, "\n"+log.String()) + log.Reset() + + buf, err = os.ReadFile(joinPath(tmpDir, fileName)) + require.NoError(t, err) + require.Equal(t, "abc", string(buf)) +} + func Test_fdFdstatSetFlags(t *testing.T) { tmpDir := t.TempDir() // open before loop to ensure no locking problems. @@ -561,6 +597,13 @@ func Test_fdFdstatSetFlags(t *testing.T) { `, "\n"+log.String()) // FIXME? flags==0 prints 'flags=' log.Reset() + requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdSeekName, uint64(fd), uint64(0), uint64(0), uint64(1024)) + require.Equal(t, ` +==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0) +<== (newoffset=0,errno=ESUCCESS) +`, "\n"+log.String()) + log.Reset() + // Without O_APPEND flag, the data is written at the beginning. writeWazero() requireFileContent("wazero6789" + "wazero") @@ -573,6 +616,16 @@ func Test_fdFdstatSetFlags(t *testing.T) { `, "\n"+log.String()) // FIXME? flags==1 prints 'flags=APPEND' log.Reset() + // Restoring the O_APPEND flag should not reset fd offset. + requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdTellName, uint64(fd), uint64(1024)) + require.Equal(t, ` +==> wasi_snapshot_preview1.fd_tell(fd=4,result.offset=1024) +<== errno=ESUCCESS +`, "\n"+log.String()) + log.Reset() + offset, _ := mod.Memory().Read(1024, 4) + require.Equal(t, offset, []byte{6, 0, 0, 0}) + // with O_APPEND flag, the data is appended to buffer. writeWazero() requireFileContent("wazero6789" + "wazero" + "wazero") @@ -782,7 +835,7 @@ func Test_fdFilestatSetSize(t *testing.T) { tests := []struct { name string - size uint32 + size uint64 content, expectedContent []byte expectedLog string expectedErrno wasip1.Errno @@ -828,6 +881,17 @@ func Test_fdFilestatSetSize(t *testing.T) { expectedLog: ` ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=106) <== errno=ESUCCESS +`, + }, + { + name: "large size", + content: []byte(""), + expectedContent: []byte(""), + size: math.MaxUint64, + expectedErrno: wasip1.ErrnoInval, + expectedLog: ` +==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=-1) +<== errno=EINVAL `, }, } diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go index 0eb705c6..8cc15963 100644 --- a/internal/engine/wazevo/backend/compiler.go +++ b/internal/engine/wazevo/backend/compiler.go @@ -15,16 +15,11 @@ func NewCompiler(ctx context.Context, mach Machine, builder ssa.Builder) Compile return newCompiler(ctx, mach, builder) } -func newCompiler(ctx context.Context, mach Machine, builder ssa.Builder) *compiler { - registerSetDebug := false - if wazevoapi.RegAllocValidationEnabled { - registerSetDebug = wazevoapi.IsHighRegisterPressure(ctx) - } - +func newCompiler(_ context.Context, mach Machine, builder ssa.Builder) *compiler { c := &compiler{ mach: mach, ssaBuilder: builder, nextVRegID: regalloc.VRegIDNonReservedBegin, - regAlloc: regalloc.NewAllocator(mach.RegisterInfo(registerSetDebug)), + regAlloc: regalloc.NewAllocator(mach.RegisterInfo()), } mach.SetCompiler(c) return c diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go index fa9b8387..2a8c36c5 100644 --- a/internal/engine/wazevo/backend/isa/arm64/instr.go +++ b/internal/engine/wazevo/backend/isa/arm64/instr.go @@ -647,7 +647,7 @@ func (i *instruction) brLabel() label { } // brOffsetResolved is called when the target label is resolved. -func (i *instruction) brOffsetResolved(offset int64) { +func (i *instruction) brOffsetResolve(offset int64) { i.u2 = uint64(offset) i.u3 = 1 // indicate that the offset is resolved, for debugging. } @@ -666,6 +666,10 @@ func (i *instruction) asCondBr(c cond, target label, is64bit bool) { } } +func (i *instruction) setCondBrTargets(target label) { + i.u2 = uint64(target) +} + func (i *instruction) condBrLabel() label { return label(i.u2) } diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go index ba5c25c7..48a9282b 100644 --- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go +++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go @@ -1280,7 +1280,7 @@ func TestInstruction_encode(t *testing.T) { }}, {want: "20000014", setup: func(i *instruction) { i.asBr(dummyLabel) - i.brOffsetResolved(0x80) + i.brOffsetResolve(0x80) }}, {want: "01040034", setup: func(i *instruction) { i.asCondBr(registerAsRegZeroCond(x1VReg), dummyLabel, false) diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go index 47a4e4d4..20f535b4 100644 --- a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go +++ b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go @@ -245,11 +245,11 @@ func (m *machine) lowerLoadSplat(ptr ssa.Value, offset uint32, lane ssa.VecLane, } amode := m.lowerToAddressMode(ptr, offset, opSize) rd := operandNR(m.compiler.VRegOf(ret)) - m.lowerLoadSplatFromAddressMode(rd, amode, opSize, lane) + m.lowerLoadSplatFromAddressMode(rd, amode, lane) } // lowerLoadSplatFromAddressMode is extracted from lowerLoadSplat for testing. -func (m *machine) lowerLoadSplatFromAddressMode(rd operand, amode addressMode, opSize byte, lane ssa.VecLane) { +func (m *machine) lowerLoadSplatFromAddressMode(rd operand, amode addressMode, lane ssa.VecLane) { tmpReg := operandNR(m.compiler.AllocateVReg(ssa.TypeI64)) // vecLoad1R has offset address mode (base+imm) only for post index, so the only addressing mode @@ -264,12 +264,15 @@ func (m *machine) lowerLoadSplatFromAddressMode(rd operand, amode addressMode, o add.asALU(aluOpAdd, tmpReg, operandNR(amode.rn), operandImm12(uint16(amode.imm), 0), true) m.insert(add) case addressModeKindRegUnsignedImm12: - offsetReg := operandNR(m.compiler.AllocateVReg(ssa.TypeI64)) - m.load64bitConst(amode.imm, offsetReg.nr()) - - add := m.allocateInstr() - m.insert(add) - add.asALU(aluOpAdd, tmpReg, operandNR(amode.rn), offsetReg, true) + if amode.imm != 0 { + offsetReg := m.compiler.AllocateVReg(ssa.TypeI64) + m.load64bitConst(amode.imm, offsetReg) + add := m.allocateInstr() + m.insert(add) + add.asALU(aluOpAdd, tmpReg, operandNR(amode.rn), operandNR(offsetReg), true) + } else { + tmpReg = operandNR(amode.rn) + } default: panic("unsupported address mode for LoadSplat") } diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go index aac54a18..b9700680 100644 --- a/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go +++ b/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go @@ -856,24 +856,30 @@ func Test_lowerLoadSplatFromAddressMode(t *testing.T) { expectPanic bool }{ { - amode: addressMode{kind: addressModeKindRegReg, rn: v0VReg, rm: v1VReg}, + amode: addressMode{kind: addressModeKindRegReg, rn: x0VReg, rm: x1VReg}, expected: ` -add x100?, d0, d1 +add x100?, x0, x1 ld1r {x10.4s}, [x100?] `, }, { - amode: addressMode{kind: addressModeKindRegUnsignedImm12, rn: v0VReg, imm: 15616}, + amode: addressMode{kind: addressModeKindRegUnsignedImm12, rn: x0VReg, imm: 15616}, expected: ` movz x101?, #0x3d00, lsl 0 -add x100?, d0, x101? +add x100?, x0, x101? ld1r {x10.4s}, [x100?] `, }, { - amode: addressMode{kind: addressModeKindRegSignedImm9, rn: v0VReg, imm: 42}, + amode: addressMode{kind: addressModeKindRegUnsignedImm12, rn: x15VReg, imm: 0}, expected: ` -add x100?, d0, #0x2a +ld1r {x10.4s}, [x15] +`, + }, + { + amode: addressMode{kind: addressModeKindRegSignedImm9, rn: x0VReg, imm: 42}, + expected: ` +add x100?, x0, #0x2a ld1r {x10.4s}, [x100?] `, }, @@ -884,7 +890,7 @@ ld1r {x10.4s}, [x100?] ctx.vRegCounter = int(nextVReg.ID()) - 1 positiveTests[tc.amode.kind] = true - m.lowerLoadSplatFromAddressMode(operandNR(x10VReg), tc.amode, 32, ssa.VecLaneI32x4) + m.lowerLoadSplatFromAddressMode(operandNR(x10VReg), tc.amode, ssa.VecLaneI32x4) require.Equal(t, tc.expected, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n") }) } @@ -901,7 +907,7 @@ ld1r {x10.4s}, [x100?] t.Run("address mode "+strconv.Itoa(k), func(t *testing.T) { err := require.CapturePanic(func() { - m.lowerLoadSplatFromAddressMode(operandNR(x10VReg), addressMode{kind: amk}, 32, ssa.VecLaneI32x4) + m.lowerLoadSplatFromAddressMode(operandNR(x10VReg), addressMode{kind: amk}, ssa.VecLaneI32x4) }) require.Contains(t, err.Error(), "unsupported address mode for LoadSplat") }) diff --git a/internal/engine/wazevo/backend/isa/arm64/machine.go b/internal/engine/wazevo/backend/isa/arm64/machine.go index a6af31c8..c1d16ef6 100644 --- a/internal/engine/wazevo/backend/isa/arm64/machine.go +++ b/internal/engine/wazevo/backend/isa/arm64/machine.go @@ -43,6 +43,9 @@ type ( addends64 queue[regalloc.VReg] unresolvedAddressModes []*instruction + // condBrRelocs holds the conditional branches which need offset relocation. + condBrRelocs []condBrReloc + // spillSlotSize is the size of the stack slot in bytes used for spilling registers. // During the execution of the function, the stack looks like: // @@ -98,10 +101,20 @@ type ( // labelPosition represents the regions of the generated code which the label represents. labelPosition struct { + l label begin, end *instruction binarySize int64 binaryOffset int64 } + + condBrReloc struct { + cbr *instruction + // currentLabelPos is the labelPosition within which condBr is defined. + currentLabelPos *labelPosition + // Next block's labelPosition. + nextLabel label + offset int64 + } ) const ( @@ -205,7 +218,7 @@ func (m *machine) StartBlock(blk ssa.BasicBlock) { labelPos, ok := m.labelPositions[l] if !ok { - labelPos = m.allocateLabelPosition() + labelPos = m.allocateLabelPosition(l) m.labelPositions[l] = labelPos } m.orderedBlockLabels = append(m.orderedBlockLabels, labelPos) @@ -231,18 +244,24 @@ func (m *machine) insert(i *instruction) { } func (m *machine) insertBrTargetLabel() label { - l := m.allocateLabel() - nop := m.allocateInstr() - nop.asNop0WithLabel(l) + nop, l := m.allocateBrTarget() m.insert(nop) - pos := m.allocateLabelPosition() + return l +} + +func (m *machine) allocateBrTarget() (nop *instruction, l label) { + l = m.allocateLabel() + nop = m.allocateInstr() + nop.asNop0WithLabel(l) + pos := m.allocateLabelPosition(l) pos.begin, pos.end = nop, nop m.labelPositions[l] = pos - return l + return } -func (m *machine) allocateLabelPosition() *labelPosition { +func (m *machine) allocateLabelPosition(la label) *labelPosition { l := m.labelPositionPool.Allocate() + l.l = la return l } @@ -344,17 +363,34 @@ func (m *machine) ResolveRelativeAddresses() { } } + // Reuse the slice to gather the unresolved conditional branches. + cbrs := m.condBrRelocs[:0] + // Next, in order to determine the offsets of relative jumps, we have to calculate the size of each label. var offset int64 - for _, pos := range m.orderedBlockLabels { + for i, pos := range m.orderedBlockLabels { pos.binaryOffset = offset var size int64 for cur := pos.begin; ; cur = cur.next { - if cur.kind == nop0 { + switch cur.kind { + case nop0: l := cur.nop0Label() if pos, ok := m.labelPositions[l]; ok { pos.binaryOffset = offset + size } + case condBr: + if !cur.condBrOffsetResolved() { + var nextLabel label + if i < len(m.orderedBlockLabels)-1 { + // Note: this is only used when the block ends with fallthrough, + // therefore can be safely assumed that the next block exists when it's needed. + nextLabel = m.orderedBlockLabels[i+1].l + } + cbrs = append(cbrs, condBrReloc{ + cbr: cur, currentLabelPos: pos, offset: offset + size, + nextLabel: nextLabel, + }) + } } size += cur.size() if cur == pos.end { @@ -365,6 +401,30 @@ func (m *machine) ResolveRelativeAddresses() { offset += size } + // Before resolving any offsets, we need to check if all the conditional branches can be resolved. + var needRerun bool + for i := range cbrs { + reloc := &cbrs[i] + cbr := reloc.cbr + offset := reloc.offset + + target := cbr.condBrLabel() + offsetOfTarget := m.labelPositions[target].binaryOffset + diff := offsetOfTarget - offset + if divided := diff >> 2; divided < minSignedInt19 || divided > maxSignedInt19 { + // This case the conditional branch is too huge. We place the trampoline instructions at the end of the current block, + // and jump to it. + m.insertConditionalJumpTrampoline(cbr, reloc.currentLabelPos, reloc.nextLabel) + // Then, we need to recall this function to fix up the label offsets + // as they have changed after the trampoline is inserted. + needRerun = true + } + } + if needRerun { + m.ResolveRelativeAddresses() + return + } + var currentOffset int64 for cur := m.rootInstr; cur != nil; cur = cur.next { switch cur.kind { @@ -372,29 +432,19 @@ func (m *machine) ResolveRelativeAddresses() { target := cur.brLabel() offsetOfTarget := m.labelPositions[target].binaryOffset diff := offsetOfTarget - currentOffset - if diff%4 != 0 { - panic("BUG: offsets between b and the target must be a multiple of 4") - } divided := diff >> 2 if divided < minSignedInt26 || divided > maxSignedInt26 { // This means the currently compiled single function is extremely large. - panic("BUG: implement branch relocation for large unconditional branch larger than 26-bit range") + panic("too large function that requires branch relocation of large unconditional branch larger than 26-bit range") } - cur.brOffsetResolved(diff) + cur.brOffsetResolve(diff) case condBr: if !cur.condBrOffsetResolved() { target := cur.condBrLabel() offsetOfTarget := m.labelPositions[target].binaryOffset diff := offsetOfTarget - currentOffset - if diff%4 != 0 { - panic("BUG: offsets between b and the target must be a multiple of 4") - } - divided := diff >> 2 - if divided < minSignedInt19 || divided > maxSignedInt19 { - // This case we can insert "trampoline block" in the middle and jump to it. - // After that, we need to re-calculate the offset of labels after the trampoline block by - // recursively calling this function. - panic("TODO: implement branch relocation for large conditional branch larger than 19-bit range") + if divided := diff >> 2; divided < minSignedInt19 || divided > maxSignedInt19 { + panic("BUG: branch relocation for large conditional branch larger than 19-bit range must be handled properly") } cur.condBrOffsetResolve(diff) } @@ -421,6 +471,35 @@ const ( minSignedInt19 int64 = -(1 << 19) ) +func (m *machine) insertConditionalJumpTrampoline(cbr *instruction, currentBlk *labelPosition, nextLabel label) { + cur := currentBlk.end + originalTarget := cbr.condBrLabel() + endNext := cur.next + + if cur.kind != br { + // If the current block ends with a conditional branch, we can just insert the trampoline after it. + // Otherwise, we need to insert "skip" instruction to skip the trampoline instructions. + skip := m.allocateInstr() + skip.asBr(nextLabel) + cur = linkInstr(cur, skip) + } + + cbrNewTargetInstr, cbrNewTargetLabel := m.allocateBrTarget() + cbr.setCondBrTargets(cbrNewTargetLabel) + cur = linkInstr(cur, cbrNewTargetInstr) + + // Then insert the unconditional branch to the original, which should be possible to get encoded + // as 26-bit offset should be enough for any practical application. + br := m.allocateInstr() + br.asBr(originalTarget) + cur = linkInstr(cur, br) + + // Update the end of the current block. + currentBlk.end = cur + + linkInstr(cur, endNext) +} + func (m *machine) getOrAllocateSSABlockLabel(blk ssa.BasicBlock) label { if blk.ReturnBlock() { return returnLabel diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go index 9744a533..2920c932 100644 --- a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go +++ b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go @@ -320,25 +320,7 @@ func (r *regAllocBlockImpl) BlockParams(regs *[]regalloc.VReg) []regalloc.VReg { func (r *regAllocBlockImpl) Entry() bool { return r.sb.EntryBlock() } // RegisterInfo implements backend.Machine. -func (m *machine) RegisterInfo(debug bool) *regalloc.RegisterInfo { - if debug { - regInfoDebug := ®alloc.RegisterInfo{} - regInfoDebug.CalleeSavedRegisters = regInfo.CalleeSavedRegisters - regInfoDebug.CallerSavedRegisters = regInfo.CallerSavedRegisters - regInfoDebug.RealRegToVReg = regInfo.RealRegToVReg - regInfoDebug.RealRegName = regInfo.RealRegName - regInfoDebug.RealRegType = regInfo.RealRegType - regInfoDebug.AllocatableRegisters[regalloc.RegTypeFloat] = []regalloc.RealReg{ - v18, // One callee saved. - v7, v6, v5, v4, v3, v2, v1, v0, // Allocatable sets == Argument registers. - } - regInfoDebug.AllocatableRegisters[regalloc.RegTypeInt] = []regalloc.RealReg{ - x29, x30, // Caller saved, and special ones. But they should be able to get allocated. - x19, // One callee saved. - x7, x6, x5, x4, x3, x2, x1, x0, // Argument registers (all caller saved). - } - return regInfoDebug - } +func (m *machine) RegisterInfo() *regalloc.RegisterInfo { return regInfo } diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_test.go b/internal/engine/wazevo/backend/isa/arm64/machine_test.go index f48c1ca8..78574af8 100644 --- a/internal/engine/wazevo/backend/isa/arm64/machine_test.go +++ b/internal/engine/wazevo/backend/isa/arm64/machine_test.go @@ -123,3 +123,100 @@ func TestMachine_getVRegSpillSlotOffsetFromSP(t *testing.T) { _, ok = m.spillSlots[id] require.True(t, ok) } + +func TestMachine_insertConditionalJumpTrampoline(t *testing.T) { + for _, tc := range []struct { + brAtEnd bool + expBefore, expAfter string + }{ + { + brAtEnd: true, + expBefore: ` +L100: + b.eq L12345 + b L888888888 +L200: + exit_sequence x0 +`, + expAfter: ` +L100: + b.eq L10000000 + b L888888888 +L10000000: + b L12345 +L200: + exit_sequence x0 +`, + }, + { + brAtEnd: false, + expBefore: ` +L100: + b.eq L12345 + udf +L200: + exit_sequence x0 +`, + expAfter: ` +L100: + b.eq L10000000 + udf + b L200 +L10000000: + b L12345 +L200: + exit_sequence x0 +`, + }, + } { + var name string + if tc.brAtEnd { + name = "brAtEnd" + } else { + name = "brNotAtEnd" + } + + t.Run(name, func(t *testing.T) { + m := NewBackend().(*machine) + const ( + originLabel = 100 + originLabelNext = 200 + targetLabel = 12345 + ) + + cbr := m.allocateInstr() + cbr.asCondBr(eq.asCond(), targetLabel, false) + + end := m.allocateInstr() + if tc.brAtEnd { + end.asBr(888888888) + } else { + end.asUDF() + } + + originalEndNext := m.allocateInstr() + originalEndNext.asExitSequence(x0VReg) + + originLabelPos := m.allocateLabelPosition(originLabel) + originLabelPos.begin = cbr + originLabelPos.end = linkInstr(cbr, end) + originNextLabelPos := m.allocateLabelPosition(originLabelNext) + originNextLabelPos.begin = originalEndNext + linkInstr(originLabelPos.end, originalEndNext) + + m.labelPositions[originLabel] = originLabelPos + m.labelPositions[originLabelNext] = originNextLabelPos + + m.rootInstr = cbr + require.Equal(t, tc.expBefore, m.Format()) + + m.nextLabel = 9999999 + m.insertConditionalJumpTrampoline(cbr, originLabelPos, originLabelNext) + + require.Equal(t, tc.expAfter, m.Format()) + + // The original label position should be updated to the unconditional jump to the original target destination. + require.Equal(t, "b L12345", originLabelPos.end.String()) + }) + } +} diff --git a/internal/engine/wazevo/backend/machine.go b/internal/engine/wazevo/backend/machine.go index eef68247..843ca02a 100644 --- a/internal/engine/wazevo/backend/machine.go +++ b/internal/engine/wazevo/backend/machine.go @@ -14,9 +14,7 @@ type ( // RegisterInfo returns the set of registers that can be used for register allocation. // This is only called once, and the result is shared across all compilations. - // - // If debug is true, this returns the register set for debugging purpose. - RegisterInfo(debug bool) *regalloc.RegisterInfo + RegisterInfo() *regalloc.RegisterInfo // InitializeABI initializes the FunctionABI for the given signature. InitializeABI(sig *ssa.Signature) diff --git a/internal/engine/wazevo/backend/machine_test.go b/internal/engine/wazevo/backend/machine_test.go index 17e87540..2b71d517 100644 --- a/internal/engine/wazevo/backend/machine_test.go +++ b/internal/engine/wazevo/backend/machine_test.go @@ -56,7 +56,7 @@ func (m mockMachine) ResolveRelativeAddresses() {} func (m mockMachine) Function() (f regalloc.Function) { return } // RegisterInfo implements Machine.RegisterInfo. -func (m mockMachine) RegisterInfo(bool) *regalloc.RegisterInfo { +func (m mockMachine) RegisterInfo() *regalloc.RegisterInfo { if m.rinfo != nil { return m.rinfo } diff --git a/internal/engine/wazevo/backend/regalloc/regalloc.go b/internal/engine/wazevo/backend/regalloc/regalloc.go index f8c184e9..85a5218f 100644 --- a/internal/engine/wazevo/backend/regalloc/regalloc.go +++ b/internal/engine/wazevo/backend/regalloc/regalloc.go @@ -21,6 +21,7 @@ func NewAllocator(allocatableRegs *RegisterInfo) Allocator { a := Allocator{ regInfo: allocatableRegs, blockLivenessDataPool: wazevoapi.NewPool[blockLivenessData](resetBlockLivenessData), + phiDefInstListPool: wazevoapi.NewPool[phiDefInstList](resetPhiDefInstList), } a.state.reset() for _, regs := range allocatableRegs.AllocatableRegisters { @@ -56,10 +57,10 @@ type ( blockLivenessData [] /* blockID to */ *blockLivenessData vs []VReg maxBlockID int + phiDefInstListPool wazevoapi.Pool[phiDefInstList] // Followings are re-used during various places e.g. coloring. blks []Block - insts []Instr reals []RealReg currentOccupants regInUseSet @@ -115,11 +116,23 @@ type ( lastUse programCounter // isPhi is true if this is a phi value. isPhi bool - // isArg is true if this is phi (isPhi=true) and the value is passed via a real register at the beginning of the blk. - regPhi RealReg + // phiDefInstList is a list of instructions that defines this phi value. + // This is used to determine the spill location, and only valid if isPhi=true. + *phiDefInstList + } + + // phiDefInstList is a linked list of instructions that defines a phi value. + phiDefInstList struct { + instr Instr + next *phiDefInstList } ) +func resetPhiDefInstList(l *phiDefInstList) { + l.instr = nil + l.next = nil +} + func (s *state) dump(info *RegisterInfo) { //nolint:unused fmt.Println("\t\tstate:") fmt.Println("\t\t\targRealRegs:", s.argRealRegs) @@ -178,7 +191,7 @@ func (vs *vrState) reset() { vs.spilled = false vs.lca = nil vs.isPhi = false - vs.regPhi = RealRegInvalid + vs.phiDefInstList = nil } func (s *state) getVRegState(v VReg) *vrState { @@ -531,7 +544,6 @@ func (a *Allocator) allocBlock(f Function, blk Block) { } pc = 0 - a.insts = a.insts[:0] for instr := blk.InstrIteratorBegin(); instr != nil; instr = blk.InstrIteratorNext() { if wazevoapi.RegAllocLoggingEnabled { fmt.Println(instr) @@ -641,11 +653,10 @@ func (a *Allocator) allocBlock(f Function, blk Block) { fmt.Printf("\tdefining v%d with %s\n", def.ID(), a.regInfo.RealRegName(r)) } if vState.isPhi { - if blk.Entry() { - // If this is the entry block, the phi value has a unique definition. - vState.defInstr = instr - } - a.insts = append(a.insts, instr) + n := a.phiDefInstListPool.Allocate() + n.instr = instr + n.next = vState.phiDefInstList + vState.phiDefInstList = n } else { vState.defInstr = instr vState.defBlk = blk @@ -658,16 +669,6 @@ func (a *Allocator) allocBlock(f Function, blk Block) { pc++ } - if !blk.Entry() { - for _, phiDefInstr := range a.insts { - phiDefInstr.Defs(&a.vs) - phi := a.vs[0] - if s.getVRegState(phi).r == RealRegInvalid { - f.StoreRegisterAfter(phi, phiDefInstr) - } - } - } - s.regsInUse.range_(func(allocated RealReg, v VReg) { currentBlkState.endRegs.add(allocated, v) }) @@ -723,14 +724,6 @@ func (a *Allocator) fixMergeState(f Function, blk Block) { fmt.Println("fixMergeState", blk.ID(), ":", desiredOccupants.format(a.regInfo)) } - // Record that register-allocated phis and not. - for _, phi := range blk.BlockParams(&a.vs) { - vs := s.getVRegState(phi) - if r, ok := aliveOnRegVRegs[phi]; ok { - vs.regPhi = r - } - } - currentOccupants := &a.currentOccupants for i := 0; i < preds; i++ { currentOccupants.reset() @@ -833,11 +826,12 @@ func (a *Allocator) reconcileEdge(f Function, ) } f.SwapAtEndOfBlock( - FromRealReg(r, typ), - FromRealReg(er, typ), + currentVReg.SetRealReg(r), + desiredVReg.SetRealReg(er), freeReg, pred, ) + s.allocatedRegSet = s.allocatedRegSet.add(freeReg.RealReg()) currentOccupantsRev[desiredVReg] = r currentOccupantsRev[currentVReg] = er currentOccupants.add(r, desiredVReg) @@ -884,21 +878,11 @@ func (a *Allocator) scheduleSpills(f Function) { func (a *Allocator) scheduleSpill(f Function, vs *vrState) { v := vs.v - // If the value is the phi value, we need to insert a spill before the first instruction of the defining block whose - // arguments contain the value. - if phiDefiningBlk := vs.defBlk; vs.isPhi && - // Except for the entry block since the phi value is actually defined via the instruction. - !phiDefiningBlk.Entry() { - if r := vs.regPhi; r == RealRegInvalid { - // This case, the phi is already passed via stack performed in the code inserted in fixMergeState function. - if wazevoapi.RegAllocLoggingEnabled { - fmt.Printf("v%d is already passed via stack at blk%v\n", v.ID(), phiDefiningBlk.ID()) - fmt.Println(vs.defInstr) - a.blockStates[phiDefiningBlk.ID()].dump(a.regInfo) - } - } else { - // Otherwise, we need to insert a spill before the first instruction of the block. - f.StoreRegisterAfter(v.SetRealReg(r), phiDefiningBlk.FirstInstr()) + // If the value is the phi value, we need to insert a spill after each phi definition. + if vs.isPhi { + for defInstr := vs.phiDefInstList; defInstr != nil; defInstr = defInstr.next { + def := defInstr.instr.Defs(&a.vs)[0] + f.StoreRegisterAfter(def, defInstr.instr) } return } @@ -954,6 +938,7 @@ func (a *Allocator) Reset() { s.reset() } a.blockLivenessDataPool.Reset() + a.phiDefInstListPool.Reset() a.vs = a.vs[:0] a.maxBlockID = -1 diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go index d0d09167..d8bbec63 100644 --- a/internal/engine/wazevo/call_engine.go +++ b/internal/engine/wazevo/call_engine.go @@ -395,7 +395,7 @@ func opaqueViewFromPtr(ptr uintptr) []byte { return opaque } -const callStackCeiling = uintptr(5000000) // in uint64 (8 bytes) == 40000000 bytes in total == 40mb. +const callStackCeiling = uintptr(50000000) // in uint64 (8 bytes) == 400000000 bytes in total == 400mb. func (c *callEngine) growStackWithGuarded() (newSP uintptr, err error) { if wazevoapi.StackGuardCheckEnabled { diff --git a/internal/engine/wazevo/config.go b/internal/engine/wazevo/config.go deleted file mode 100644 index 8e221fa0..00000000 --- a/internal/engine/wazevo/config.go +++ /dev/null @@ -1,42 +0,0 @@ -package wazevo - -import ( - "context" - "unsafe" - - wazero "github.com/wasilibs/wazerox" - "github.com/wasilibs/wazerox/api" - "github.com/wasilibs/wazerox/internal/filecache" - "github.com/wasilibs/wazerox/internal/wasm" -) - -// ConfigureWazevo modifies wazero.RuntimeConfig and sets the wazevo implementation. -// This is a hack to avoid modifying outside the wazevo package while testing it end-to-end. -// -// Until we expose it in the experimental public API, use this for internal testing. -func ConfigureWazevo(config wazero.RuntimeConfig) { - // This is the internal representation of interface in Go. - // https://research.swtch.com/interfaces - type iface struct { - _ *byte - data unsafe.Pointer - } - - configInterface := (*iface)(unsafe.Pointer(&config)) - - // This corresponds to the unexported wazero.runtimeConfig, and the target field newEngine exists - // in the middle of the implementation. - type newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine - type runtimeConfig struct { - enabledFeatures api.CoreFeatures - memoryLimitPages uint32 - memoryCapacityFromMax bool - engineKind int - dwarfDisabled bool - newEngine - // Other fields follow, but we don't care. - } - cm := (*runtimeConfig)(configInterface.data) - // Insert the wazevo implementation. - cm.newEngine = NewEngine -} diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go index 89eadf29..03290b6a 100644 --- a/internal/engine/wazevo/e2e_test.go +++ b/internal/engine/wazevo/e2e_test.go @@ -12,7 +12,7 @@ import ( "github.com/wasilibs/wazerox/api" "github.com/wasilibs/wazerox/experimental" "github.com/wasilibs/wazerox/experimental/logging" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/internal/engine/wazevo/testcases" "github.com/wasilibs/wazerox/internal/leb128" "github.com/wasilibs/wazerox/internal/testing/binaryencoding" @@ -308,10 +308,7 @@ func TestE2E(t *testing.T) { t.Run(name, func(t *testing.T) { cache, err := wazero.NewCompilationCacheWithDir(tmp) require.NoError(t, err) - config := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cache) - - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler().WithCompilationCache(cache) ctx := context.Background() r := wazero.NewRuntimeWithConfig(ctx, config) @@ -377,10 +374,7 @@ func TestE2E_host_functions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { ctx := tc.ctx - config := wazero.NewRuntimeConfigCompiler() - - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() r := wazero.NewRuntimeWithConfig(ctx, config) defer func() { @@ -462,10 +456,7 @@ func TestE2E_host_functions(t *testing.T) { } func TestE2E_stores(t *testing.T) { - config := wazero.NewRuntimeConfigCompiler() - - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.Background() r := wazero.NewRuntimeWithConfig(ctx, config) @@ -552,10 +543,7 @@ func TestE2E_reexported_memory(t *testing.T) { CodeSection: []wasm.Code{{Body: []byte{wasm.OpcodeI32Const, 10, wasm.OpcodeMemoryGrow, 0, wasm.OpcodeEnd}}}, } - config := wazero.NewRuntimeConfigCompiler() - - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.Background() r := wazero.NewRuntimeWithConfig(ctx, config) @@ -604,10 +592,7 @@ func TestStackUnwind_panic_in_host(t *testing.T) { }, } - config := wazero.NewRuntimeConfigCompiler() - - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.Background() r := wazero.NewRuntimeWithConfig(ctx, config) @@ -657,11 +642,7 @@ func TestStackUnwind_unreachable(t *testing.T) { }, } - config := wazero.NewRuntimeConfigCompiler() - - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) - + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.Background() r := wazero.NewRuntimeWithConfig(ctx, config) defer func() { @@ -683,12 +664,9 @@ wasm stack trace: func TestListener_local(t *testing.T) { var buf bytes.Buffer - config := wazero.NewRuntimeConfigCompiler() + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) - r := wazero.NewRuntimeWithConfig(ctx, config) defer func() { require.NoError(t, r.Close(ctx)) @@ -714,12 +692,9 @@ func TestListener_local(t *testing.T) { func TestListener_imported(t *testing.T) { var buf bytes.Buffer - config := wazero.NewRuntimeConfigCompiler() + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) - r := wazero.NewRuntimeWithConfig(ctx, config) defer func() { require.NoError(t, r.Close(ctx)) @@ -768,12 +743,9 @@ func TestListener_long(t *testing.T) { }) var buf bytes.Buffer - config := wazero.NewRuntimeConfigCompiler() + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) - r := wazero.NewRuntimeWithConfig(ctx, config) defer func() { require.NoError(t, r.Close(ctx)) @@ -821,12 +793,9 @@ func TestListener_long_as_is(t *testing.T) { }) var buf bytes.Buffer - config := wazero.NewRuntimeConfigCompiler() + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) - r := wazero.NewRuntimeWithConfig(ctx, config) defer func() { require.NoError(t, r.Close(ctx)) @@ -873,12 +842,9 @@ func TestListener_long_many_consts(t *testing.T) { }) var buf bytes.Buffer - config := wazero.NewRuntimeConfigCompiler() + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) - r := wazero.NewRuntimeWithConfig(ctx, config) defer func() { require.NoError(t, r.Close(ctx)) @@ -901,12 +867,9 @@ func TestListener_long_many_consts(t *testing.T) { // TestDWARF verifies that the DWARF based stack traces work as expected before/after compilation cache. func TestDWARF(t *testing.T) { - config := wazero.NewRuntimeConfigCompiler() + config := opt.NewRuntimeConfigOptimizingCompiler() ctx := context.Background() - // Configure the new optimizing backend! - wazevo.ConfigureWazevo(config) - bin := dwarftestdata.ZigWasm dir := t.TempDir() diff --git a/internal/engine/wazevo/wazevoapi/debug_options.go b/internal/engine/wazevo/wazevoapi/debug_options.go index a846a6e2..01bfff50 100644 --- a/internal/engine/wazevo/wazevoapi/debug_options.go +++ b/internal/engine/wazevo/wazevoapi/debug_options.go @@ -41,18 +41,15 @@ const ( ) // ----- Validations ----- -// These consts must be enabled by default until we reach the point where we can disable them (e.g. multiple days of fuzzing passes). - const ( - RegAllocValidationEnabled = true - SSAValidationEnabled = true + // SSAValidationEnabled enables the SSA validation. This is disabled by default since the operation is expensive. + SSAValidationEnabled = false ) // ----- Stack Guard Check ----- -// These consts must be enabled by default until we reach the point where we can disable them (e.g. multiple days of fuzzing passes). const ( // StackGuardCheckEnabled enables the stack guard check to ensure that our stack bounds check works correctly. - StackGuardCheckEnabled = true + StackGuardCheckEnabled = false StackGuardCheckGuardPageSize = 8096 ) diff --git a/internal/integration_test/engine/adhoc_test.go b/internal/integration_test/engine/adhoc_test.go index 6317550e..492dc03e 100644 --- a/internal/integration_test/engine/adhoc_test.go +++ b/internal/integration_test/engine/adhoc_test.go @@ -18,8 +18,8 @@ import ( "github.com/wasilibs/wazerox/api" "github.com/wasilibs/wazerox/experimental" "github.com/wasilibs/wazerox/experimental/logging" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/experimental/table" - "github.com/wasilibs/wazerox/internal/engine/wazevo" "github.com/wasilibs/wazerox/internal/leb128" "github.com/wasilibs/wazerox/internal/platform" "github.com/wasilibs/wazerox/internal/testing/binaryencoding" @@ -96,8 +96,7 @@ func TestEngineWazevo(t *testing.T) { if runtime.GOARCH != "arm64" { t.Skip() } - config := wazero.NewRuntimeConfigInterpreter() - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() runAllTests(t, tests, config.WithCloseOnContextDone(true), true) } diff --git a/internal/integration_test/engine/dwarf_test.go b/internal/integration_test/engine/dwarf_test.go index 55e5472d..11125cf6 100644 --- a/internal/integration_test/engine/dwarf_test.go +++ b/internal/integration_test/engine/dwarf_test.go @@ -8,8 +8,8 @@ import ( "testing" wazero "github.com/wasilibs/wazerox" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/imports/wasi_snapshot_preview1" - "github.com/wasilibs/wazerox/internal/engine/wazevo" "github.com/wasilibs/wazerox/internal/platform" "github.com/wasilibs/wazerox/internal/testing/dwarftestdata" "github.com/wasilibs/wazerox/internal/testing/require" @@ -37,8 +37,7 @@ func TestEngineWazevo_DWARF(t *testing.T) { if runtime.GOARCH != "arm64" { t.Skip() } - config := wazero.NewRuntimeConfigInterpreter() - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() runAllTests(t, dwarfTests, config, true) } diff --git a/internal/integration_test/engine/hammer_test.go b/internal/integration_test/engine/hammer_test.go index c5cf464a..9399ab91 100644 --- a/internal/integration_test/engine/hammer_test.go +++ b/internal/integration_test/engine/hammer_test.go @@ -8,7 +8,7 @@ import ( wazero "github.com/wasilibs/wazerox" "github.com/wasilibs/wazerox/api" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/internal/platform" "github.com/wasilibs/wazerox/internal/testing/hammer" "github.com/wasilibs/wazerox/internal/testing/require" @@ -36,8 +36,7 @@ func TestEngineWazevo_hammer(t *testing.T) { if runtime.GOARCH != "arm64" { t.Skip() } - c := wazero.NewRuntimeConfigInterpreter() - wazevo.ConfigureWazevo(c) + c := opt.NewRuntimeConfigOptimizingCompiler() runAllTests(t, hammers, c, true) } diff --git a/internal/integration_test/engine/memleak_test.go b/internal/integration_test/engine/memleak_test.go index e8eab85e..85fa9057 100644 --- a/internal/integration_test/engine/memleak_test.go +++ b/internal/integration_test/engine/memleak_test.go @@ -10,7 +10,7 @@ import ( "time" wazero "github.com/wasilibs/wazerox" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" ) func TestMemoryLeak(t *testing.T) { @@ -61,8 +61,7 @@ func testMemoryLeakInstantiateRuntimeAndModule(isWazevo bool) error { var r wazero.Runtime if isWazevo { - c := wazero.NewRuntimeConfigInterpreter() - wazevo.ConfigureWazevo(c) + c := opt.NewRuntimeConfigOptimizingCompiler() r = wazero.NewRuntimeWithConfig(ctx, c) } else { r = wazero.NewRuntime(ctx) diff --git a/internal/integration_test/filecache/filecache_test.go b/internal/integration_test/filecache/filecache_test.go index 357681d0..89619d17 100644 --- a/internal/integration_test/filecache/filecache_test.go +++ b/internal/integration_test/filecache/filecache_test.go @@ -15,7 +15,7 @@ import ( "github.com/wasilibs/wazerox/api" "github.com/wasilibs/wazerox/experimental" "github.com/wasilibs/wazerox/experimental/logging" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/internal/integration_test/spectest" v1 "github.com/wasilibs/wazerox/internal/integration_test/spectest/v1" "github.com/wasilibs/wazerox/internal/platform" @@ -35,8 +35,7 @@ func TestFileCacheSpecTest_wazevo(t *testing.T) { if runtime.GOARCH != "arm64" { return } - config := wazero.NewRuntimeConfigCompiler() - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() runAllFileCacheTests(t, config) } diff --git a/internal/integration_test/fuzz/Cargo.lock b/internal/integration_test/fuzz/Cargo.lock index cf3e4e42..20f3fe14 100644 --- a/internal/integration_test/fuzz/Cargo.lock +++ b/internal/integration_test/fuzz/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "anyhow" version = "1.0.58" @@ -23,6 +35,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "cc" version = "1.0.73" @@ -32,6 +50,22 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ctor" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" +dependencies = [ + "quote", + "syn 2.0.40", +] + [[package]] name = "derive_arbitrary" version = "1.1.3" @@ -40,7 +74,7 @@ checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -63,9 +97,12 @@ checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", +] [[package]] name = "indexmap" @@ -84,7 +121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -119,35 +156,57 @@ dependencies = [ "once_cell", ] +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "once_cell" -version = "1.13.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "semver" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "syn" @@ -160,33 +219,50 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "unicode-ident" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasm-encoder" -version = "0.34.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14a94e06a3e2ed1af4e80cac712fed883142019ebe33c3899fd1b5e8550df9d" +checksum = "7d135e8940b69dbee0f5b0a0be9c1cd6fa8b71d774904c13a3fcfc5dc265e43d" dependencies = [ "leb128", ] [[package]] name = "wasm-smith" -version = "0.12.20" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c368a04ec656f45cf36c860dca97654d94679b260f1f1f3ef6a5bb1e43ad4fd" +checksum = "4d9a642a2aa8a998228a247036d0f34470a07afc146231bd5c22cc61b8b51e73" dependencies = [ "arbitrary", "flagset", "indexmap 2.0.0", "leb128", "wasm-encoder", - "wasmparser 0.114.0", + "wasmparser 0.117.0", ] [[package]] @@ -200,10 +276,11 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.114.0" +version = "0.117.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ef211410dcb08b037eb6d197b2398f8ef9d635c5dc5598d0dfda32094315ea3" +checksum = "9b206de0c992af9f0b51ef2fb9455623e0a19eb68f172cd8ba9cd0e46637f5ab" dependencies = [ + "hashbrown 0.14.3", "indexmap 2.0.0", "semver", ] @@ -222,7 +299,30 @@ dependencies = [ name = "wazero-fuzz-fuzz" version = "0.0.0" dependencies = [ + "ctor", + "libc", "libfuzzer-sys", + "nix", "wasm-smith", "wasmprinter", ] + +[[package]] +name = "zerocopy" +version = "0.7.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] diff --git a/internal/integration_test/fuzz/README.md b/internal/integration_test/fuzz/README.md index d78179aa..cf1e6889 100644 --- a/internal/integration_test/fuzz/README.md +++ b/internal/integration_test/fuzz/README.md @@ -80,6 +80,16 @@ Minimize test case with: and you can use that command to "minimize" the input binary while keeping the same error. +Alternatively, you can use the following command to minimize the arbitrary input binary: + +``` +go test -c ./wazerolib -o nodiff.test && wasm-tools shrink ./predicate.sh original.{wasm,wat} -o shrinken.wasm --attempts 4294967295 +``` + +which uses `wasm-tools shrinken` command to minimize the input binary. Internally, the `predicate.sh` is invoked for each input binary +where it executes the `nodiff.test` binary which runs `TestReRunFailedRequireNoDiffCase`. + + ### Run fuzzing on wazevo Until we replace the existing compiler with the new optimizing compiler `wazevo`, diff --git a/internal/integration_test/fuzz/fuzz/Cargo.toml b/internal/integration_test/fuzz/fuzz/Cargo.toml index 06bc5a4c..412d7524 100644 --- a/internal/integration_test/fuzz/fuzz/Cargo.toml +++ b/internal/integration_test/fuzz/fuzz/Cargo.toml @@ -12,6 +12,9 @@ cargo-fuzz = true libfuzzer-sys = "0.4.7" wasm-smith = "0.12.20" wasmprinter = "0.2.39" +libc = "0.2" +nix = "0.23.0" +ctor = "0.2.6" [[bin]] name = "memory_no_diff" diff --git a/internal/integration_test/fuzz/fuzz/fuzz_targets/wazero_abi.rs b/internal/integration_test/fuzz/fuzz/fuzz_targets/wazero_abi.rs index 8314f79a..09702831 100644 --- a/internal/integration_test/fuzz/fuzz/fuzz_targets/wazero_abi.rs +++ b/internal/integration_test/fuzz/fuzz/fuzz_targets/wazero_abi.rs @@ -15,3 +15,69 @@ extern "C" { #[allow(dead_code)] pub fn validate(binary_ptr: *const u8, binary_size: usize); } + +use ctor::ctor; +use libc::SIGSTKSZ; +use nix::libc::{sigaltstack, stack_t}; +use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; +use std::ptr::null_mut; + +#[ctor] +/// Sets up the separate stack for signal handlers, and sets the SA_ONSTACK flag for signals that are handled by libFuzzer +/// https://github.com/llvm/llvm-project/blob/8eff5704829ba5edd28754fd9ec7665b34fde22a/compiler-rt/lib/fuzzer/FuzzerUtilPosix.cpp#L117-L141 +/// in order to ensure that Go's stacks won't get corrupted accidentally. +/// +/// This is necessary due to the undocumented requirement/behavior of Go runtime, and for detail, +/// see the detailed comments in `tests/sigstack.rs`. +fn setup_sig_handlers() { + set_signal_stack(); + set_sa_on_stack(Signal::SIGABRT); + set_sa_on_stack(Signal::SIGALRM); + set_sa_on_stack(Signal::SIGBUS); + set_sa_on_stack(Signal::SIGFPE); + set_sa_on_stack(Signal::SIGILL); + set_sa_on_stack(Signal::SIGINT); + set_sa_on_stack(Signal::SIGSEGV); + set_sa_on_stack(Signal::SIGTERM); + set_sa_on_stack(Signal::SIGXFSZ); + set_sa_on_stack(Signal::SIGUSR1); + set_sa_on_stack(Signal::SIGUSR2); +} + +/// Sets the SA_ONSTACK flag for the given signal. +fn set_sa_on_stack(sig: Signal) { + let old_action = unsafe { + let tmp = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()); + sigaction(sig, &tmp).unwrap() + }; + // Create a new SigAction with the SA_ONSTACK flag added. + let new_flags = old_action.flags() | SaFlags::SA_ONSTACK; + let new_action = SigAction::new(old_action.handler(), new_flags, old_action.mask()); + unsafe { + sigaction(sig, &new_action).unwrap(); + } +} + +/// Sets up the separate stack for signal handlers. +fn set_signal_stack() { + // Allocate a new stack for signal handlers to run on. + const STACK_SIZE: usize = SIGSTKSZ * 2; + let mut stack = vec![0u8; STACK_SIZE]; + + let stack_ptr = stack.as_mut_ptr(); + + let signal_stack = stack_t { + ss_sp: stack_ptr as *mut libc::c_void, + ss_flags: 0, + ss_size: STACK_SIZE, + }; + + unsafe { + if sigaltstack(&signal_stack, null_mut()) != 0 { + panic!("Failed to set alternate signal stack"); + } + + // Leak the stack vector to prevent it from being dropped. + std::mem::forget(stack); + } +} diff --git a/internal/integration_test/fuzz/fuzz/tests/sigstack.rs b/internal/integration_test/fuzz/fuzz/tests/sigstack.rs new file mode 100644 index 00000000..f85e23de --- /dev/null +++ b/internal/integration_test/fuzz/fuzz/tests/sigstack.rs @@ -0,0 +1,69 @@ +extern crate libc; +extern crate nix; + +use libc::SIGSTKSZ; +use libc::{pthread_kill, pthread_self, SIGUSR1}; +use nix::sys::signal; +use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet}; +use std::thread; +use std::time::Duration; + +const STACK_SIZE: usize = SIGSTKSZ * 4; + +#[test] +fn main() { + unsafe { + let sa = SigAction::new( + SigHandler::SigAction(handler), + // Set SA_ONSTACK to ensure the signal handler runs on the alternate stack. + // The alternate stack is prepared by the Go runtime if there's not the one by the host C program + // via the sigaltstack syscall. However if this flag is not set, which happens when + // the host C program not having intention to deal with signals gracefully (e.g. stack overflow), + // the signal handler (installed by either C program or Go runtime) will run on + // the "current stack". That is problematic when a signal handling happens during execution + // of wazevo function because it uses "Go allocated" stack. + // + // On the other hand, this is more of a general problem for any C program that uses Go as a library, + // not limited to wazevo, when it does not not install sig handlers with SA_ONSTACK. That means + // any Gorountime stack could result in being used during signal handling, which can potentially + // cause any memory corruption. I would say such C program is using Go library in a dangerous way. + // + // To reproduce the failure in wazevo, Use SaFlags::empty() and wazevoapi.StackGuardCheckEnabled=true. + // + // Note that this only happens a Go program is compiled as c-archive or c-shared. If it is + // used normally, the signal handlers are installed on each signal by the Go runtime, which + // sets SA_ONSTACK and proper alternate stack, hence there's no way the current stack is used + // during singal handling. + SaFlags::SA_ONSTACK, + SigSet::empty(), + ); + + if let Err(err) = sigaction(signal::SIGUSR1, &sa) { + panic!("Failed to set signal handler: {}", err); + } + + let main_thread_id = pthread_self(); + thread::spawn(move || loop { + thread::sleep(Duration::from_millis(1)); + pthread_kill(main_thread_id, SIGUSR1); + }); + test_signal_stack(); + } +} + +extern "C" fn handler(_: libc::c_int, _: *mut libc::siginfo_t, _: *mut libc::c_void) { + // Declare a large local array to use the stack space. + let mut large_array: [u8; 1024] = [0; 1024]; + + // Use the array to prevent compiler optimizations from removing it. + for i in 0..large_array.len() { + large_array[i] = i as u8; + } + if large_array[100] != 100 { + panic!("large_array[0] != 0"); + } +} + +extern "C" { + pub fn test_signal_stack(); +} diff --git a/internal/integration_test/fuzz/predicate.sh b/internal/integration_test/fuzz/predicate.sh new file mode 100755 index 00000000..de376eed --- /dev/null +++ b/internal/integration_test/fuzz/predicate.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# The Wasm file is given as the first and only argument to the script. +WASM=$1 + +echo "Testing $WASM" + +export WASM_BINARY_PATH=$WASM + +# Run the test and reverse the exit code so that a non-zero exit code indicates interesting case. +./nodiff.test +exit $((! $?)) diff --git a/internal/integration_test/fuzz/wazerolib/lib.go b/internal/integration_test/fuzz/wazerolib/lib.go index d03597cd..f9a72a76 100644 --- a/internal/integration_test/fuzz/wazerolib/lib.go +++ b/internal/integration_test/fuzz/wazerolib/lib.go @@ -2,14 +2,22 @@ package main import "C" import ( + "context" "crypto/sha256" + _ "embed" "encoding/hex" "fmt" + "math" "os" "path" + "runtime" + "strings" wazero "github.com/wasilibs/wazerox" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" + "github.com/wasilibs/wazerox/internal/leb128" + "github.com/wasilibs/wazerox/internal/testing/binaryencoding" + "github.com/wasilibs/wazerox/internal/wasm" ) func main() {} @@ -77,7 +85,79 @@ To reproduce the failure, execute: WASM_BINARY_PATH=%s go test -run=%s ./wazerol func newCompilerConfig() wazero.RuntimeConfig { c := wazero.NewRuntimeConfigCompiler() if os.Getenv("WAZERO_FUZZ_WAZEVO") != "" { - wazevo.ConfigureWazevo(c) + c = opt.NewRuntimeConfigOptimizingCompiler() } return c } + +//export test_signal_stack +func test_signal_stack() { + if runtime.GOARCH != "arm64" { // TODO: amd64. + return + } + // (module + // (func (export "long_loop") + // (loop + // global.get 0 + // i32.eqz + // if ;; label = @1 + // unreachable + // end + // global.get 0 + // i32.const 1 + // i32.sub + // global.set 0 + // br 0 + // ) + // ) + // (global (;0;) (mut i32) i32.const 2147483648) + // ) + bin := binaryencoding.EncodeModule(&wasm.Module{ + TypeSection: []wasm.FunctionType{{}}, + FunctionSection: []wasm.Index{0}, + GlobalSection: []wasm.Global{{ + Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}, + Init: wasm.ConstantExpression{ + Opcode: wasm.OpcodeI32Const, + Data: leb128.EncodeInt32(math.MaxInt32), + }, + }}, + ExportSection: []wasm.Export{{Type: wasm.ExternTypeFunc, Name: "long_loop", Index: 0}}, + CodeSection: []wasm.Code{ + { + Body: []byte{ + wasm.OpcodeLoop, 0, + wasm.OpcodeGlobalGet, 0, + wasm.OpcodeI32Eqz, + wasm.OpcodeIf, 0, + wasm.OpcodeUnreachable, + wasm.OpcodeEnd, + wasm.OpcodeGlobalGet, 0, + wasm.OpcodeI32Const, 1, + wasm.OpcodeI32Sub, + wasm.OpcodeGlobalSet, 0, + wasm.OpcodeBr, 0, + wasm.OpcodeEnd, + wasm.OpcodeEnd, + }, + }, + }, + }) + ctx := context.Background() + config := opt.NewRuntimeConfigOptimizingCompiler() + r := wazero.NewRuntimeWithConfig(ctx, config) + module, err := r.Instantiate(ctx, bin) + if err != nil { + panic(err) + } + defer func() { + if err = module.Close(ctx); err != nil { + panic(err) + } + }() + + _, err = module.ExportedFunction("long_loop").Call(ctx) + if !strings.Contains(err.Error(), "unreachable") { + panic("long_loop should be unreachable") + } +} diff --git a/internal/integration_test/fuzz/wazerolib/nodiff.go b/internal/integration_test/fuzz/wazerolib/nodiff.go index edf8a658..a79412dd 100644 --- a/internal/integration_test/fuzz/wazerolib/nodiff.go +++ b/internal/integration_test/fuzz/wazerolib/nodiff.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "reflect" + "sort" "strings" "unsafe" @@ -275,8 +276,16 @@ const valueTypeVector = 0x7b func ensureInvocationResultMatch(compiledMod, interpreterMod api.Module, exportedFunctions map[string]api.FunctionDefinition) (err error) { ctx := context.Background() + // In order to do the deterministic execution, we need to sort the exported functions. + var names []string + for f := range exportedFunctions { + names = append(names, f) + } + sort.Strings(names) + outer: - for name, def := range exportedFunctions { + for _, name := range names { + def := exportedFunctions[name] resultTypes := def.ResultTypes() for _, rt := range resultTypes { switch rt { @@ -349,10 +358,13 @@ func ensureInvocationError(compilerErr, interpErr error) error { interpErrMsg = interpErrMsg[:strings.Index(interpErrMsg, "\n")] } - if strings.Contains(compilerErrMsg, "stack overflow") && strings.Contains(interpErrMsg, "unreachable") { + if compiledStackOverFlow := strings.Contains(compilerErrMsg, "stack overflow"); compiledStackOverFlow && strings.Contains(interpErrMsg, "unreachable") { // Compiler is more likely to reach stack overflow than interpreter, so we allow this case. This case is most likely // that interpreter reached the unreachable out of "fuel". return nil + } else if interpreterStackOverFlow := strings.Contains(interpErrMsg, "stack overflow"); compiledStackOverFlow && interpreterStackOverFlow { + // Both compiler and interpreter reached stack overflow, so we ignore diff in the content of the traces. + return nil } if compilerErrMsg != interpErrMsg { diff --git a/internal/integration_test/fuzzcases/fuzzcases_test.go b/internal/integration_test/fuzzcases/fuzzcases_test.go index 55683aa3..0b12f584 100644 --- a/internal/integration_test/fuzzcases/fuzzcases_test.go +++ b/internal/integration_test/fuzzcases/fuzzcases_test.go @@ -4,12 +4,13 @@ import ( "context" "embed" "fmt" + "math" "runtime" "testing" wazero "github.com/wasilibs/wazerox" "github.com/wasilibs/wazerox/api" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/internal/platform" "github.com/wasilibs/wazerox/internal/testing/binaryencoding" "github.com/wasilibs/wazerox/internal/testing/require" @@ -51,8 +52,7 @@ func runWithInterpreter(t *testing.T, runner func(t *testing.T, r wazero.Runtime func runWithWazevo(t *testing.T, runner func(t *testing.T, r wazero.Runtime)) { t.Run("wazevo", func(t *testing.T) { - config := wazero.NewRuntimeConfigInterpreter() - wazevo.ConfigureWazevo(config) + config := opt.NewRuntimeConfigOptimizingCompiler() r := wazero.NewRuntimeWithConfig(ctx, config) defer r.Close(ctx) runner(t, r) @@ -719,7 +719,7 @@ func Test1825(t *testing.T) { }) } -// Test1825 tests that lowerFcopysignImpl allocates correctly the temporary registers. +// Test1826 tests that lowerFcopysignImpl allocates correctly the temporary registers. func Test1826(t *testing.T) { if !platform.CompilerSupported() { return @@ -734,3 +734,18 @@ func Test1826(t *testing.T) { require.Equal(t, uint64(0), m.Globals[0].ValHi) }) } + +func Test1846(t *testing.T) { + if !platform.CompilerSupported() { + return + } + run(t, func(t *testing.T, r wazero.Runtime) { + mod, err := r.Instantiate(ctx, getWasmBinary(t, "1846")) + require.NoError(t, err) + m := mod.(*wasm.ModuleInstance) + _, err = m.ExportedFunction("").Call(ctx) + require.NoError(t, err) + require.Equal(t, math.Float64bits(2), m.Globals[0].Val) + require.Equal(t, uint64(0), m.Globals[0].ValHi) + }) +} diff --git a/internal/integration_test/fuzzcases/testdata/1846.wasm b/internal/integration_test/fuzzcases/testdata/1846.wasm new file mode 100644 index 00000000..915bfdcf Binary files /dev/null and b/internal/integration_test/fuzzcases/testdata/1846.wasm differ diff --git a/internal/integration_test/fuzzcases/testdata/1846.wat b/internal/integration_test/fuzzcases/testdata/1846.wat new file mode 100644 index 00000000..dd38c951 --- /dev/null +++ b/internal/integration_test/fuzzcases/testdata/1846.wat @@ -0,0 +1,105 @@ +(module + (type (;0;) (func (result f64 f64 f32 f64 f64 f32 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64))) + (type (;1;) (func (result f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64))) + (type (;2;) (func)) + (func (;0;) (type 1) (result f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64) + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + f64.const 0.0 + +;; unreachable + ) + (func (;1;) (type 2) + (local f64 f64 i32) + i32.const 0 + if (type 0) (result f64 f64 f32 f64 f64 f32 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64 f64) ;; label = @1 + f64.const 1.0 + call 0 + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + f64.const 0x0p+0 (;=0;) + f32.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f32.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + else + f64.const 2.0 + f64.const 0x0p+0 (;=0;) + f32.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f32.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const -0x1.a2d89e0481a8dp+83 (;=-15823560583422023000000000;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + f64.const 0x0p+0 (;=0;) + end + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + drop + i64.reinterpret_f64 + global.set 0 + ) + (global (;0;) (mut i64) i64.const 0) + (global (;1;) (mut i32) i32.const 0) + (export "" (func 1)) +) diff --git a/internal/integration_test/spectest/v1/spec_test.go b/internal/integration_test/spectest/v1/spec_test.go index bf342504..357a4dee 100644 --- a/internal/integration_test/spectest/v1/spec_test.go +++ b/internal/integration_test/spectest/v1/spec_test.go @@ -7,7 +7,7 @@ import ( wazero "github.com/wasilibs/wazerox" "github.com/wasilibs/wazerox/api" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/internal/integration_test/spectest" "github.com/wasilibs/wazerox/internal/platform" ) @@ -24,10 +24,9 @@ func TestInterpreter(t *testing.T) { } func TestWazevo(t *testing.T) { - c := wazero.NewRuntimeConfigCompiler().WithCoreFeatures(api.CoreFeaturesV1) if runtime.GOARCH != "arm64" { t.Skip() } - wazevo.ConfigureWazevo(c) + c := opt.NewRuntimeConfigOptimizingCompiler().WithCoreFeatures(api.CoreFeaturesV1) spectest.Run(t, Testcases, context.Background(), c) } diff --git a/internal/integration_test/spectest/v2/spec_test.go b/internal/integration_test/spectest/v2/spec_test.go index e84b8be3..01400e72 100644 --- a/internal/integration_test/spectest/v2/spec_test.go +++ b/internal/integration_test/spectest/v2/spec_test.go @@ -7,7 +7,7 @@ import ( wazero "github.com/wasilibs/wazerox" "github.com/wasilibs/wazerox/api" - "github.com/wasilibs/wazerox/internal/engine/wazevo" + "github.com/wasilibs/wazerox/experimental/opt" "github.com/wasilibs/wazerox/internal/integration_test/spectest" "github.com/wasilibs/wazerox/internal/platform" ) @@ -26,10 +26,9 @@ func TestInterpreter(t *testing.T) { } func TestWazevo(t *testing.T) { - c := wazero.NewRuntimeConfigCompiler().WithCoreFeatures(enabledFeatures) if runtime.GOARCH != "arm64" { t.Skip() } - wazevo.ConfigureWazevo(c) + c := opt.NewRuntimeConfigOptimizingCompiler().WithCoreFeatures(enabledFeatures) spectest.Run(t, Testcases, context.Background(), c) } diff --git a/internal/integration_test/vs/wasmedge/wasmedge.go b/internal/integration_test/vs/wasmedge/wasmedge.go index 50798d3f..7029e479 100644 --- a/internal/integration_test/vs/wasmedge/wasmedge.go +++ b/internal/integration_test/vs/wasmedge/wasmedge.go @@ -12,8 +12,7 @@ import ( "os" "github.com/second-state/WasmEdge-go/wasmedge" - - "github.com/tetratelabs/wazero/internal/integration_test/vs" + "github.com/wasilibs/wazerox/internal/integration_test/vs" ) func newWasmedgeRuntime() vs.Runtime { diff --git a/internal/integration_test/vs/wasmedge/wasmedge_test.go b/internal/integration_test/vs/wasmedge/wasmedge_test.go index 1f20c11d..8ddc7985 100644 --- a/internal/integration_test/vs/wasmedge/wasmedge_test.go +++ b/internal/integration_test/vs/wasmedge/wasmedge_test.go @@ -7,7 +7,7 @@ package wasmedge import ( "testing" - "github.com/tetratelabs/wazero/internal/integration_test/vs" + "github.com/wasilibs/wazerox/internal/integration_test/vs" ) var runtime = newWasmedgeRuntime diff --git a/internal/integration_test/vs/wasmtime/wasmtime.go b/internal/integration_test/vs/wasmtime/wasmtime.go index 9ba1794a..517497f5 100644 --- a/internal/integration_test/vs/wasmtime/wasmtime.go +++ b/internal/integration_test/vs/wasmtime/wasmtime.go @@ -7,8 +7,7 @@ import ( "fmt" wt "github.com/bytecodealliance/wasmtime-go/v9" - - "github.com/tetratelabs/wazero/internal/integration_test/vs" + "github.com/wasilibs/wazerox/internal/integration_test/vs" ) func newWasmtimeRuntime() vs.Runtime { diff --git a/internal/integration_test/vs/wasmtime/wasmtime_test.go b/internal/integration_test/vs/wasmtime/wasmtime_test.go index 8e6235f8..da8d6446 100644 --- a/internal/integration_test/vs/wasmtime/wasmtime_test.go +++ b/internal/integration_test/vs/wasmtime/wasmtime_test.go @@ -5,7 +5,7 @@ package wasmtime import ( "testing" - "github.com/tetratelabs/wazero/internal/integration_test/vs" + "github.com/wasilibs/wazerox/internal/integration_test/vs" ) var runtime = newWasmtimeRuntime diff --git a/internal/sysfs/file_test.go b/internal/sysfs/file_test.go index 6618cf56..08b2cb76 100644 --- a/internal/sysfs/file_test.go +++ b/internal/sysfs/file_test.go @@ -147,6 +147,9 @@ func TestFileSetAppend(t *testing.T) { require.EqualErrno(t, 0, f.SetAppend(false)) require.False(t, f.IsAppend()) + _, errno = f.Seek(0, 0) + require.EqualErrno(t, 0, errno) + // without O_APPEND flag, the data writes at offset zero _, errno = f.Write([]byte("wazero")) require.EqualErrno(t, 0, errno) diff --git a/internal/sysfs/osfile.go b/internal/sysfs/osfile.go index a5a9afa3..6597c4f7 100644 --- a/internal/sysfs/osfile.go +++ b/internal/sysfs/osfile.go @@ -83,8 +83,8 @@ func (f *osFile) SetAppend(enable bool) (errno experimentalsys.Errno) { f.flag &= ^experimentalsys.O_APPEND } - // Clear any create flag, as we are re-opening, not re-creating. - f.flag &= ^experimentalsys.O_CREAT + // Clear any create or trunc flag, as we are re-opening, not re-creating. + f.flag &= ^(experimentalsys.O_CREAT | experimentalsys.O_TRUNC) // appendMode (bool) cannot be changed later, so we have to re-open the // file. https://github.com/golang/go/blob/go1.20/src/os/file_unix.go#L60 @@ -98,9 +98,38 @@ func (f *osFile) reopen() (errno experimentalsys.Errno) { // Clear any create flag, as we are re-opening, not re-creating. f.flag &= ^experimentalsys.O_CREAT + var ( + isDir bool + offset int64 + err error + ) + + isDir, errno = f.IsDir() + if errno != 0 { + return errno + } + + if !isDir { + offset, err = f.file.Seek(0, io.SeekCurrent) + if err != nil { + return experimentalsys.UnwrapOSError(err) + } + } + _ = f.close() f.file, errno = OpenFile(f.path, f.flag, f.perm) - return + if errno != 0 { + return errno + } + + if !isDir { + _, err = f.file.Seek(offset, io.SeekStart) + if err != nil { + return experimentalsys.UnwrapOSError(err) + } + } + + return 0 } // IsNonblock implements the same method as documented on fsapi.File diff --git a/site/content/_index.md b/site/content/_index.md index 70f39406..29d3ea8f 100644 --- a/site/content/_index.md +++ b/site/content/_index.md @@ -22,7 +22,7 @@ curl https://wazero.io/install.sh | sh **Embed wazero** in your Go project and extend any app ```go -import "github.com/tetratelabs/wazero" +import "github.com/wasilibs/wazerox" // ... @@ -55,12 +55,12 @@ production sites. You can get the latest version of wazero like this. ```bash -go get github.com/tetratelabs/wazero@latest +go get github.com/wasilibs/wazerox@latest ``` Please give us a [star][4] if you end up using wazero! -[1]: https://github.com/tetratelabs/wazero/blob/main/examples -[2]: https://github.com/tetratelabs/wazero/blob/main/examples/basic +[1]: https://github.com/wasilibs/wazerox/blob/main/examples +[2]: https://github.com/wasilibs/wazerox/blob/main/examples/basic [3]: https://tetrate.io/blog/introducing-wazero-from-tetrate/ -[4]: https://github.com/tetratelabs/wazero/stargazers +[4]: https://github.com/wasilibs/wazerox/stargazers diff --git a/site/content/community/users.md b/site/content/community/users.md index c84e59e2..a08558fe 100644 --- a/site/content/community/users.md +++ b/site/content/community/users.md @@ -25,6 +25,7 @@ considering their efforts before starting your own! | Name | Description | |:-------------------------------|-------------------------------------------------------------------------------| +| [Extism][38] | Simplified cross-language extensibility in Go & a dozen+ languages| | [go-plugin][2] | implements [Protocol Buffers][8] services with WebAssembly vi code generation | | [waPC][5] | implements [Apex][6] interfaces with WebAssembly via code generation | | [wazero-emscripten-embind][36] | Emscripten [Embind][37] and code generation support for Wazero | @@ -142,3 +143,5 @@ experience. [36]: https://github.com/jerbob92/wazero-emscripten-embind [37]: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html + +[38]: https://github.com/extism/extism