diff --git a/go.mod b/go.mod index 3f37e78..2eeeff4 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/wasilibs/go-re2 go 1.20 require ( - github.com/tetratelabs/wazero v1.7.1 + github.com/tetratelabs/wazero v1.7.2 github.com/wasilibs/nottinygc v0.4.0 ) -require github.com/magefile/mage v1.14.0 // indirect +require github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a // indirect diff --git a/go.sum b/go.sum index 664a764..90c6ce6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ -github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= -github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8= -github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a h1:tdPcGgyiH0K+SbsJBBm2oPyEIOTAvLBwD9TuUwVtZho= +github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= +github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ= github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo= diff --git a/internal/alloc/alloc_notunix.go b/internal/alloc/alloc_notunix.go index 541730f..0aab41e 100644 --- a/internal/alloc/alloc_notunix.go +++ b/internal/alloc/alloc_notunix.go @@ -1,4 +1,4 @@ -//go:build !unix +//go:build !unix && !windows package alloc diff --git a/internal/alloc/alloc.go b/internal/alloc/alloc_unix.go similarity index 96% rename from internal/alloc/alloc.go rename to internal/alloc/alloc_unix.go index aec76e2..2847cfa 100644 --- a/internal/alloc/alloc.go +++ b/internal/alloc/alloc_unix.go @@ -71,7 +71,9 @@ type mmappedMemory struct { } func (m *mmappedMemory) Reallocate(size uint64) []byte { - if com := uint64(len(m.buf)); com < size { + com := uint64(len(m.buf)) + res := uint64(cap(m.buf)) + if com < size && size < res { // Round up to the page size. rnd := uint64(syscall.Getpagesize() - 1) new := (size + rnd) &^ rnd @@ -85,7 +87,7 @@ func (m *mmappedMemory) Reallocate(size uint64) []byte { // Update committed memory. m.buf = m.buf[:new] } - return m.buf[:size] + return m.buf[:size:len(m.buf)] } func (m *mmappedMemory) Free() { diff --git a/internal/alloc/alloc_windows.go b/internal/alloc/alloc_windows.go new file mode 100644 index 0000000..d499aff --- /dev/null +++ b/internal/alloc/alloc_windows.go @@ -0,0 +1,100 @@ +//go:build windows + +package alloc + +import ( + "fmt" + "math" + "syscall" + "unsafe" + + "github.com/tetratelabs/wazero/experimental" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procVirtualAlloc = kernel32.NewProc("VirtualAlloc") + procVirtualFree = kernel32.NewProc("VirtualFree") +) + +const ( + windows_MEM_COMMIT uintptr = 0x00001000 + windows_MEM_RESERVE uintptr = 0x00002000 + windows_MEM_RELEASE uintptr = 0x00008000 + windows_PAGE_READWRITE uintptr = 0x00000004 + + // https://cs.opensource.google/go/x/sys/+/refs/tags/v0.20.0:windows/syscall_windows.go;l=131 + windows_PAGE_SIZE uint64 = 4096 +) + +func Allocator() experimental.MemoryAllocator { + return experimental.MemoryAllocatorFunc(virtualAllocator) +} + +func virtualAllocator(cap, max uint64) experimental.LinearMemory { + // Round up to the page size. + rnd := windows_PAGE_SIZE - 1 + max = (max + rnd) &^ rnd + cap = (cap + rnd) &^ rnd + + if max > math.MaxInt { + // This ensures uintptr(max) overflows to a large value, + // and windows.VirtualAlloc returns an error. + max = math.MaxUint64 + } + + // Reserve, but don't commit, max bytes of address space, to ensure we won't need to move it. + // This does not commit memory. + r, _, err := procVirtualAlloc.Call(0, uintptr(max), windows_MEM_RESERVE, windows_PAGE_READWRITE) + if r == 0 { + panic(fmt.Errorf("alloc_windows: failed to reserve memory: %w", err)) + } + + // Commit the initial cap bytes of memory. + r, _, err = procVirtualAlloc.Call(r, uintptr(cap), windows_MEM_COMMIT, windows_PAGE_READWRITE) + if r == 0 { + _, _, _ = procVirtualFree.Call(r, 0, windows_MEM_RELEASE) + panic(fmt.Errorf("alloc_windows: failed to commit initial memory: %w", err)) + } + + buf := unsafe.Slice((*byte)(unsafe.Pointer(r)), int(max)) + return &virtualMemory{buf: buf[:cap], addr: r} +} + +// The slice covers the entire allocated memory: +// - len(buf) is the already committed memory, +// - cap(buf) is the reserved address space. +type virtualMemory struct { + buf []byte + addr uintptr +} + +func (m *virtualMemory) Reallocate(size uint64) []byte { + com := uint64(len(m.buf)) + res := uint64(cap(m.buf)) + if com < size && size < res { + // Round up to the page size. + rnd := windows_PAGE_SIZE - 1 + new := (size + rnd) &^ rnd + + // Commit additional memory up to new bytes. + r, _, err := procVirtualAlloc.Call(m.addr, uintptr(new), windows_MEM_COMMIT, windows_PAGE_READWRITE) + if r == 0 { + panic(fmt.Errorf("alloc_windows: failed to commit memory: %w", err)) + } + + // Limit returned capacity because bytes beyond + // len(m.buf) have not yet been committed. + m.buf = m.buf[:new] + } + return m.buf[:size:len(m.buf)] +} + +func (m *virtualMemory) Free() { + r, _, err := procVirtualFree.Call(m.addr, 0, windows_MEM_RELEASE) + if r == 0 { + panic(fmt.Errorf("alloc_windows: failed to release memory: %w", err)) + } + m.addr = 0 + m.buf = nil +} diff --git a/internal/e2e/go.mod b/internal/e2e/go.mod index 30ee236..5a579ee 100644 --- a/internal/e2e/go.mod +++ b/internal/e2e/go.mod @@ -4,9 +4,6 @@ go 1.20 require github.com/wasilibs/go-re2 v1.5.2 -require ( - github.com/magefile/mage v1.14.0 // indirect - github.com/tetratelabs/wazero v1.7.1 // indirect -) +require github.com/tetratelabs/wazero v1.7.2 // indirect replace github.com/wasilibs/go-re2 => ../.. diff --git a/internal/e2e/go.sum b/internal/e2e/go.sum index fffd7ad..9c087f0 100644 --- a/internal/e2e/go.sum +++ b/internal/e2e/go.sum @@ -1,4 +1,3 @@ -github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= -github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8= +github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a h1:tdPcGgyiH0K+SbsJBBm2oPyEIOTAvLBwD9TuUwVtZho= +github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ= diff --git a/internal/re2_wazero.go b/internal/re2_wazero.go index 89496f3..b9805fc 100644 --- a/internal/re2_wazero.go +++ b/internal/re2_wazero.go @@ -19,6 +19,7 @@ import ( "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/experimental" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "github.com/wasilibs/go-re2/internal/alloc" "github.com/wasilibs/go-re2/internal/memory" ) @@ -160,25 +161,26 @@ func init() { rtCfg := wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV2 | experimental.CoreFeaturesThreads) + maxPages := defaultMaxPages if a := alloc.Allocator(); a != nil { ctx = experimental.WithMemoryAllocator(ctx, a) - } else { - maxPages := defaultMaxPages - if m := memory.TotalMemory(); m != 0 { - pages := uint32(m / 65536) // Divide by WASM page size - if pages < maxPages { - maxPages = pages - } + } else if m := memory.TotalMemory(); m != 0 { + pages := uint32(m / 65536) // Divide by WASM page size + if pages < maxPages { + maxPages = pages } - if unsafe.Sizeof(uintptr(0)) < 8 { - // On a 32-bit system. anything more than 1GB is likely to fail so we cap to it. - maxPagesLimit := uint32(65536 / 4) - if maxPages > maxPagesLimit { - maxPages = maxPagesLimit - } + } + + if unsafe.Sizeof(uintptr(0)) < 8 { + // On a 32-bit system. anything close to 4GB will fail (part of 4GB is already used by the rest of the process). + // We go ahead and cap to 1GB to to be extra conservative. It will be using interpreter mode anyways so either + // the memory limit or the performance will be an issue either way. + maxPagesLimit := uint32(65536 / 4) + if maxPages > maxPagesLimit { + maxPages = maxPagesLimit } - rtCfg = rtCfg.WithMemoryLimitPages(maxPages) } + rtCfg = rtCfg.WithMemoryLimitPages(maxPages) rt := wazero.NewRuntimeWithConfig(ctx, rtCfg) diff --git a/magefiles/go.mod b/magefiles/go.mod index d7b848c..f1c8c89 100644 --- a/magefiles/go.mod +++ b/magefiles/go.mod @@ -2,4 +2,4 @@ module github.com/wasilibs/go-re2/magefiles go 1.20 -require github.com/magefile/mage v1.14.0 +require github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a diff --git a/magefiles/go.sum b/magefiles/go.sum index f8bfb2f..caf7676 100644 --- a/magefiles/go.sum +++ b/magefiles/go.sum @@ -1,2 +1 @@ -github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= -github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a h1:tdPcGgyiH0K+SbsJBBm2oPyEIOTAvLBwD9TuUwVtZho= diff --git a/wafbench/go.mod b/wafbench/go.mod index a13d49c..8e5cade 100644 --- a/wafbench/go.mod +++ b/wafbench/go.mod @@ -24,14 +24,14 @@ require ( github.com/imdario/mergo v0.3.13 // indirect github.com/knadh/koanf v1.4.4 // indirect github.com/kyokomi/emoji v2.2.4+incompatible // indirect - github.com/magefile/mage v1.14.0 // indirect + github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 // indirect - github.com/tetratelabs/wazero v1.7.1 // indirect + github.com/tetratelabs/wazero v1.7.2 // indirect github.com/tidwall/gjson v1.14.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/wafbench/go.sum b/wafbench/go.sum index f7cb9f8..1fea443 100644 --- a/wafbench/go.sum +++ b/wafbench/go.sum @@ -192,8 +192,7 @@ github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+ github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= -github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a h1:tdPcGgyiH0K+SbsJBBm2oPyEIOTAvLBwD9TuUwVtZho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -289,7 +288,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8= +github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=