wasm: implement a growable heap
On WebAssembly it is possible to grow the heap with the memory.grow instruction. This commit implements this feature and with that also removes the -heap-size flag that was reportedly broken (I haven't verified that). This should make it easier to use TinyGo for WebAssembly, where there was no good reason to use a fixed heap size. This commit has no effect on baremetal targets with optimizations enabled.
Этот коммит содержится в:
родитель
5af4c073cd
коммит
da0161d6ab
10 изменённых файлов: 115 добавлений и 47 удалений
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
|
@ -203,12 +202,6 @@ func (c *Config) LDFlags() []string {
|
|||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
ldflags = append(ldflags, "-L", root)
|
||||
if c.Target.GOARCH == "wasm" {
|
||||
// Round heap size to next multiple of 65536 (the WebAssembly page
|
||||
// size).
|
||||
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
|
||||
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
|
||||
}
|
||||
if c.Target.LinkerScript != "" {
|
||||
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ type Options struct {
|
|||
LDFlags []string
|
||||
Tags string
|
||||
WasmAbi string
|
||||
HeapSize int64
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
}
|
||||
|
|
35
main.go
35
main.go
|
@ -14,7 +14,6 @@ import (
|
|||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -650,30 +649,6 @@ func windowsFindUSBDrive(volume string) (string, error) {
|
|||
return "", errors.New("unable to locate a USB device to be flashed")
|
||||
}
|
||||
|
||||
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
|
||||
// number.
|
||||
func parseSize(s string) (int64, error) {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
if len(s) == 0 {
|
||||
return 0, errors.New("no size provided")
|
||||
}
|
||||
multiply := int64(1)
|
||||
switch s[len(s)-1] {
|
||||
case 'k':
|
||||
multiply = 1 << 10
|
||||
case 'm':
|
||||
multiply = 1 << 20
|
||||
case 'g':
|
||||
multiply = 1 << 30
|
||||
}
|
||||
if multiply != 1 {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
n, err := strconv.ParseInt(s, 0, 64)
|
||||
n *= multiply
|
||||
return n, err
|
||||
}
|
||||
|
||||
// getDefaultPort returns the default serial port depending on the operating system.
|
||||
func getDefaultPort() (port string, err error) {
|
||||
var portPath string
|
||||
|
@ -839,7 +814,6 @@ func main() {
|
|||
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
||||
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
||||
wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
||||
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
|
||||
|
||||
var flagJSON, flagDeps *bool
|
||||
if command == "help" || command == "list" {
|
||||
|
@ -893,16 +867,9 @@ func main() {
|
|||
options.LDFlags = strings.Split(*ldFlags, " ")
|
||||
}
|
||||
|
||||
var err error
|
||||
if options.HeapSize, err = parseSize(*heapSize); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Setenv("CC", "clang -target="+*target)
|
||||
|
||||
err = options.Verify()
|
||||
err := options.Verify()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
usage()
|
||||
|
|
|
@ -17,6 +17,9 @@ var heapStartSymbol [0]byte
|
|||
//export llvm.wasm.memory.size.i32
|
||||
func wasm_memory_size(index int32) int32
|
||||
|
||||
//export llvm.wasm.memory.grow.i32
|
||||
func wasm_memory_grow(index int32, delta int32) int32
|
||||
|
||||
var (
|
||||
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
|
||||
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
|
||||
|
@ -30,3 +33,20 @@ func align(ptr uintptr) uintptr {
|
|||
}
|
||||
|
||||
func getCurrentStackPointer() uintptr
|
||||
|
||||
// growHeap tries to grow the heap size. It returns true if it succeeds, false
|
||||
// otherwise.
|
||||
func growHeap() bool {
|
||||
// Grow memory by the available size, which means the heap size is doubled.
|
||||
memorySize := wasm_memory_size(0)
|
||||
result := wasm_memory_grow(0, memorySize)
|
||||
if result == -1 {
|
||||
// Grow failed.
|
||||
return false
|
||||
}
|
||||
|
||||
setHeapEnd(uintptr(wasm_memory_size(0) * wasmPageSize))
|
||||
|
||||
// Heap has grown successfully.
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -29,6 +29,13 @@ var (
|
|||
stackTop = uintptr(unsafe.Pointer(&stackTopSymbol))
|
||||
)
|
||||
|
||||
// growHeap tries to grow the heap size. It returns true if it succeeds, false
|
||||
// otherwise.
|
||||
func growHeap() bool {
|
||||
// On baremetal, there is no way the heap can be grown.
|
||||
return false
|
||||
}
|
||||
|
||||
//export malloc
|
||||
func libc_malloc(size uintptr) unsafe.Pointer {
|
||||
return alloc(size)
|
||||
|
|
|
@ -184,6 +184,50 @@ func (b gcBlock) unmark() {
|
|||
// any packages the runtime depends upon may not allocate memory during package
|
||||
// initialization.
|
||||
func initHeap() {
|
||||
calculateHeapAddresses()
|
||||
|
||||
// Set all block states to 'free'.
|
||||
metadataSize := heapEnd - uintptr(metadataStart)
|
||||
memzero(unsafe.Pointer(metadataStart), metadataSize)
|
||||
}
|
||||
|
||||
// setHeapEnd is called to expand the heap. The heap can only grow, not shrink.
|
||||
// Also, the heap should grow substantially each time otherwise growing the heap
|
||||
// will be expensive.
|
||||
func setHeapEnd(newHeapEnd uintptr) {
|
||||
if gcAsserts && newHeapEnd <= heapEnd {
|
||||
panic("gc: setHeapEnd didn't grow the heap")
|
||||
}
|
||||
|
||||
// Save some old variables we need later.
|
||||
oldMetadataStart := metadataStart
|
||||
oldMetadataSize := heapEnd - uintptr(metadataStart)
|
||||
|
||||
// Increase the heap. After setting the new heapEnd, calculateHeapAddresses
|
||||
// will update metadataStart and the memcpy will copy the metadata to the
|
||||
// new location.
|
||||
// The new metadata will be bigger than the old metadata, but a simple
|
||||
// memcpy is fine as it only copies the old metadata and the new memory will
|
||||
// have been zero initialized.
|
||||
heapEnd = newHeapEnd
|
||||
calculateHeapAddresses()
|
||||
memcpy(metadataStart, oldMetadataStart, oldMetadataSize)
|
||||
|
||||
// Note: the memcpy above assumes the heap grows enough so that the new
|
||||
// metadata does not overlap the old metadata. If that isn't true, memmove
|
||||
// should be used to avoid corruption.
|
||||
// This assert checks whether that's true.
|
||||
if gcAsserts && uintptr(metadataStart) < uintptr(oldMetadataStart)+oldMetadataSize {
|
||||
panic("gc: heap did not grow enough at once")
|
||||
}
|
||||
}
|
||||
|
||||
// calculateHeapAddresses initializes variables such as metadataStart and
|
||||
// numBlock based on heapStart and heapEnd.
|
||||
//
|
||||
// This function can be called again when the heap size increases. The caller is
|
||||
// responsible for copying the metadata to the new location.
|
||||
func calculateHeapAddresses() {
|
||||
totalSize := heapEnd - heapStart
|
||||
|
||||
// Allocate some memory to keep 2 bits of information about every block.
|
||||
|
@ -206,9 +250,6 @@ func initHeap() {
|
|||
// sanity check
|
||||
runtimePanic("gc: metadata array is too small")
|
||||
}
|
||||
|
||||
// Set all block states to 'free'.
|
||||
memzero(metadataStart, metadataSize)
|
||||
}
|
||||
|
||||
// alloc tries to find some free space on the heap, possibly doing a garbage
|
||||
|
@ -238,7 +279,16 @@ func alloc(size uintptr) unsafe.Pointer {
|
|||
GC()
|
||||
} else {
|
||||
// Even after garbage collection, no free memory could be found.
|
||||
runtimePanic("out of memory")
|
||||
// Try to increase heap size.
|
||||
if growHeap() {
|
||||
// Success, the heap was increased in size. Try again with a
|
||||
// larger heap.
|
||||
} else {
|
||||
// Unfortunately the heap could not be increased. This
|
||||
// happens on baremetal systems for example (where all
|
||||
// available RAM has already been dedicated to the heap).
|
||||
runtimePanic("out of memory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,12 @@ func alloc(size uintptr) unsafe.Pointer {
|
|||
size = align(size)
|
||||
addr := heapptr
|
||||
heapptr += size
|
||||
if heapptr >= heapEnd {
|
||||
for heapptr >= heapEnd {
|
||||
// Try to increase the heap and check again.
|
||||
if growHeap() {
|
||||
continue
|
||||
}
|
||||
// Failed to make the heap bigger, so we must really be out of memory.
|
||||
runtimePanic("out of memory")
|
||||
}
|
||||
for i := uintptr(0); i < uintptr(size); i += 4 {
|
||||
|
@ -49,3 +54,10 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
|
|||
func initHeap() {
|
||||
// Nothing to initialize.
|
||||
}
|
||||
|
||||
// setHeapEnd sets a new (larger) heapEnd pointer.
|
||||
func setHeapEnd(newHeapEnd uintptr) {
|
||||
// This "heap" is so simple that simply assigning a new value is good
|
||||
// enough.
|
||||
heapEnd = newHeapEnd
|
||||
}
|
||||
|
|
|
@ -31,3 +31,7 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
|
|||
func initHeap() {
|
||||
// Nothing to initialize.
|
||||
}
|
||||
|
||||
func setHeapEnd(newHeapEnd uintptr) {
|
||||
// Nothing to do here, this function is never actually called.
|
||||
}
|
||||
|
|
|
@ -217,6 +217,13 @@ func setupHeap() {
|
|||
}
|
||||
}
|
||||
|
||||
// growHeap tries to grow the heap size. It returns true if it succeeds, false
|
||||
// otherwise.
|
||||
func growHeap() bool {
|
||||
// Growing the heap is unimplemented.
|
||||
return false
|
||||
}
|
||||
|
||||
// getHeapBase returns the start address of the heap
|
||||
// this is externally linked by gonx
|
||||
func getHeapBase() uintptr {
|
||||
|
|
|
@ -13,3 +13,12 @@ func preinit() {
|
|||
heapStart = uintptr(malloc(heapSize))
|
||||
heapEnd = heapStart + heapSize
|
||||
}
|
||||
|
||||
// growHeap tries to grow the heap size. It returns true if it succeeds, false
|
||||
// otherwise.
|
||||
func growHeap() bool {
|
||||
// At the moment, this is not possible. However it shouldn't be too
|
||||
// difficult (at least on Linux) to allocate a large amount of virtual
|
||||
// memory at startup that is then slowly used.
|
||||
return false
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче