Skip to content

Commit 9f8aa89

Browse files
authored
Create memory module dynamically and check total memory (#79)
1 parent f5c9039 commit 9f8aa89

File tree

11 files changed

+304
-7
lines changed

11 files changed

+304
-7
lines changed

internal/memory/LICENSE

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Copied from https://github.com/pbnjay/memory
2+
3+
BSD 3-Clause License
4+
5+
Copyright (c) 2017, Jeremy Jay
6+
All rights reserved.
7+
8+
Redistribution and use in source and binary forms, with or without
9+
modification, are permitted provided that the following conditions are met:
10+
11+
* Redistributions of source code must retain the above copyright notice, this
12+
list of conditions and the following disclaimer.
13+
14+
* Redistributions in binary form must reproduce the above copyright notice,
15+
this list of conditions and the following disclaimer in the documentation
16+
and/or other materials provided with the distribution.
17+
18+
* Neither the name of the copyright holder nor the names of its
19+
contributors may be used to endorse or promote products derived from
20+
this software without specific prior written permission.
21+
22+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

internal/memory/doc.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Package memory provides a single method reporting total system memory
2+
// accessible to the kernel.
3+
package memory
4+
5+
// TotalMemory returns the total accessible system memory in bytes.
6+
//
7+
// The total accessible memory is installed physical memory size minus reserved
8+
// areas for the kernel and hardware, if such reservations are reported by
9+
// the operating system.
10+
//
11+
// If accessible memory size could not be determined, then 0 is returned.
12+
func TotalMemory() uint64 {
13+
return sysTotalMemory()
14+
}
15+
16+
// FreeMemory returns the total free system memory in bytes.
17+
//
18+
// The total free memory is installed physical memory size minus reserved
19+
// areas for other applications running on the same system.
20+
//
21+
// If free memory size could not be determined, then 0 is returned.
22+
func FreeMemory() uint64 {
23+
return sysFreeMemory()
24+
}

internal/memory/memory_bsd.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//go:build freebsd || openbsd || dragonfly || netbsd
2+
// +build freebsd openbsd dragonfly netbsd
3+
4+
package memory
5+
6+
func sysTotalMemory() uint64 {
7+
s, err := sysctlUint64("hw.physmem")
8+
if err != nil {
9+
return 0
10+
}
11+
return s
12+
}
13+
14+
func sysFreeMemory() uint64 {
15+
s, err := sysctlUint64("hw.usermem")
16+
if err != nil {
17+
return 0
18+
}
19+
return s
20+
}

internal/memory/memory_darwin.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//go:build darwin
2+
// +build darwin
3+
4+
package memory
5+
6+
import (
7+
"os/exec"
8+
"regexp"
9+
"strconv"
10+
)
11+
12+
func sysTotalMemory() uint64 {
13+
s, err := sysctlUint64("hw.memsize")
14+
if err != nil {
15+
return 0
16+
}
17+
return s
18+
}
19+
20+
var (
21+
rePageSize = regexp.MustCompile(`page size of ([0-9]*) bytes`)
22+
reFreePages = regexp.MustCompile(`Pages free: *([0-9]*)\.`)
23+
)
24+
25+
func sysFreeMemory() uint64 {
26+
cmd := exec.Command("vm_stat")
27+
outBytes, err := cmd.Output()
28+
if err != nil {
29+
return 0
30+
}
31+
32+
// default: page size of 4096 bytes
33+
matches := rePageSize.FindSubmatchIndex(outBytes)
34+
pageSize := uint64(4096)
35+
if len(matches) == 4 {
36+
pageSize, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64)
37+
if err != nil {
38+
return 0
39+
}
40+
}
41+
42+
// ex: Pages free: 1126961.
43+
matches = reFreePages.FindSubmatchIndex(outBytes)
44+
freePages := uint64(0)
45+
if len(matches) == 4 {
46+
freePages, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64)
47+
if err != nil {
48+
return 0
49+
}
50+
}
51+
return freePages * pageSize
52+
}

internal/memory/memory_linux.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//go:build linux
2+
// +build linux
3+
4+
package memory
5+
6+
import "syscall"
7+
8+
func sysTotalMemory() uint64 {
9+
in := &syscall.Sysinfo_t{}
10+
err := syscall.Sysinfo(in)
11+
if err != nil {
12+
return 0
13+
}
14+
// If this is a 32-bit system, then these fields are
15+
// uint32 instead of uint64.
16+
// So we always convert to uint64 to match signature.
17+
return uint64(in.Totalram) * uint64(in.Unit)
18+
}
19+
20+
func sysFreeMemory() uint64 {
21+
in := &syscall.Sysinfo_t{}
22+
err := syscall.Sysinfo(in)
23+
if err != nil {
24+
return 0
25+
}
26+
// If this is a 32-bit system, then these fields are
27+
// uint32 instead of uint64.
28+
// So we always convert to uint64 to match signature.
29+
return uint64(in.Freeram) * uint64(in.Unit)
30+
}

internal/memory/memory_windows.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package memory
5+
6+
import (
7+
"syscall"
8+
"unsafe"
9+
)
10+
11+
// omitting a few fields for brevity...
12+
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366589(v=vs.85).aspx
13+
type memStatusEx struct {
14+
dwLength uint32
15+
dwMemoryLoad uint32
16+
ullTotalPhys uint64
17+
ullAvailPhys uint64
18+
unused [5]uint64
19+
}
20+
21+
func sysTotalMemory() uint64 {
22+
kernel32, err := syscall.LoadDLL("kernel32.dll")
23+
if err != nil {
24+
return 0
25+
}
26+
// GetPhysicallyInstalledSystemMemory is simpler, but broken on
27+
// older versions of windows (and uses this under the hood anyway).
28+
globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx")
29+
if err != nil {
30+
return 0
31+
}
32+
msx := &memStatusEx{
33+
dwLength: 64,
34+
}
35+
r, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx)))
36+
if r == 0 {
37+
return 0
38+
}
39+
return msx.ullTotalPhys
40+
}
41+
42+
func sysFreeMemory() uint64 {
43+
kernel32, err := syscall.LoadDLL("kernel32.dll")
44+
if err != nil {
45+
return 0
46+
}
47+
// GetPhysicallyInstalledSystemMemory is simpler, but broken on
48+
// older versions of windows (and uses this under the hood anyway).
49+
globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx")
50+
if err != nil {
51+
return 0
52+
}
53+
msx := &memStatusEx{
54+
dwLength: 64,
55+
}
56+
r, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx)))
57+
if r == 0 {
58+
return 0
59+
}
60+
return msx.ullAvailPhys
61+
}

internal/memory/memsysctl.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//go:build darwin || freebsd || openbsd || dragonfly || netbsd
2+
// +build darwin freebsd openbsd dragonfly netbsd
3+
4+
package memory
5+
6+
import (
7+
"syscall"
8+
"unsafe"
9+
)
10+
11+
func sysctlUint64(name string) (uint64, error) {
12+
s, err := syscall.Sysctl(name)
13+
if err != nil {
14+
return 0, err
15+
}
16+
// hack because the string conversion above drops a \0
17+
b := []byte(s)
18+
if len(b) < 8 {
19+
b = append(b, 0)
20+
}
21+
return *(*uint64)(unsafe.Pointer(&b[0])), nil
22+
}

internal/memory/stub.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build !linux && !darwin && !windows && !freebsd && !dragonfly && !netbsd && !openbsd
2+
// +build !linux,!darwin,!windows,!freebsd,!dragonfly,!netbsd,!openbsd
3+
4+
package memory
5+
6+
func sysTotalMemory() uint64 {
7+
return 0
8+
}
9+
10+
func sysFreeMemory() uint64 {
11+
return 0
12+
}

internal/re2_wazero.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
_ "embed"
99
"encoding/binary"
10+
"encoding/hex"
1011
"errors"
1112
"os"
1213
"runtime"
@@ -18,6 +19,8 @@ import (
1819
"github.com/tetratelabs/wazero/api"
1920
"github.com/tetratelabs/wazero/experimental"
2021
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
22+
23+
"github.com/wasilibs/go-re2/internal/memory"
2124
)
2225

2326
var (
@@ -28,11 +31,6 @@ var (
2831
//go:embed wasm/libcre2.so
2932
var libre2 []byte
3033

31-
// memoryWasm created by `wat2wasm --enable-threads internal/wasm/memory.wat`
32-
//
33-
//go:embed wasm/memory.wasm
34-
var memoryWasm []byte
35-
3634
var (
3735
wasmRT wazero.Runtime
3836
wasmCompiled wazero.CompiledModule
@@ -156,7 +154,17 @@ func init() {
156154

157155
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
158156

159-
if _, err := rt.InstantiateWithConfig(ctx, memoryWasm, wazero.NewModuleConfig().WithName("env")); err != nil {
157+
maxPages := uint32(65536)
158+
if m := memory.TotalMemory(); m != 0 {
159+
pages := uint32(m / 65536) // Divide by WASM page size
160+
if pages < maxPages {
161+
maxPages = pages
162+
}
163+
}
164+
165+
mem := encodeMemory(maxPages)
166+
167+
if _, err := rt.InstantiateWithConfig(ctx, mem, wazero.NewModuleConfig().WithName("env")); err != nil {
160168
panic(err)
161169
}
162170

@@ -643,3 +651,41 @@ func (f *lazyFunction) callWithStack(ctx context.Context, callStack []uint64) (u
643651
}
644652
return callStack[0], nil
645653
}
654+
655+
// Memory module is prefix, max pages, suffix, resulting a module looking like
656+
// (module (memory (export "memory") 3 <max> shared))
657+
const (
658+
memoryPrefixHex = "0061736d010000000506010303"
659+
memorySuffixHex = "070a01066d656d6f72790200"
660+
)
661+
662+
func encodeMemory(maxPages uint32) []byte {
663+
memoryPrefix, _ := hex.DecodeString(memoryPrefixHex)
664+
memorySuffix, _ := hex.DecodeString(memorySuffixHex)
665+
pages := encodeUint64(uint64(maxPages))
666+
res := append([]byte(memoryPrefix), pages...)
667+
return append(res, []byte(memorySuffix)...)
668+
}
669+
670+
// From https://github.com/tetratelabs/wazero/blob/main/internal/leb128/leb128.go#L75
671+
672+
func encodeUint64(value uint64) (buf []byte) {
673+
// This is effectively a do/while loop where we take 7 bits of the value and encode them until it is zero.
674+
for {
675+
// Take 7 remaining low-order bits from the value into b.
676+
b := uint8(value & 0x7f)
677+
value = value >> 7
678+
679+
// If there are remaining bits, the value won't be zero: Set the high-
680+
// order bit to tell the reader there are more bytes in this uint.
681+
if value != 0 {
682+
b |= 0x80
683+
}
684+
685+
// Append b into the buffer
686+
buf = append(buf, b)
687+
if b&0x80 == 0 {
688+
return buf
689+
}
690+
}
691+
}

internal/wasm/memory.wasm

-28 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)