diff --git a/compileopts/config.go b/compileopts/config.go index 944322eb69..fe3a189fa3 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -549,6 +549,9 @@ func (c *Config) LDFlags() []string { if c.Target.LinkerScript != "" { ldflags = append(ldflags, "-T", c.Target.LinkerScript) } + if c.Target.Linker == "wasm-ld" && c.Scheduler() == "none" && c.Options.StackSize != 0 { + ldflags = append(ldflags, "-z", fmt.Sprintf("stack-size=%d", c.Options.StackSize)) + } ldflags = append(ldflags, c.Options.ExtLDFlags...) return ldflags diff --git a/src/internal/poll/fd_wasip1.go b/src/internal/poll/fd_wasip1.go index 5dcddbc26d..8ccf4020f9 100644 --- a/src/internal/poll/fd_wasip1.go +++ b/src/internal/poll/fd_wasip1.go @@ -519,6 +519,10 @@ const ( // package syscall (intentionally duplicated rather than linknamed // across the package boundary — see project memory on shim avoidance). func wait(fd int, mode uint8) { + if task.OnSystemStack() { + task.Pause() + return + } pd := runtime_netpoll_addwait(uint32(fd), mode) task.Pause() runtime_netpoll_done(pd) @@ -538,6 +542,10 @@ func (fd *FD) parkUntil(mode uint8, deadline time.Time) error { if d <= 0 { return ErrDeadlineExceeded } + if task.OnSystemStack() { + task.Pause() + return nil + } pd := runtime_netpoll_addwait(uint32(fd.Sysfd), mode) timer := time.AfterFunc(d, func() { runtime_netpoll_wake(pd) @@ -548,6 +556,15 @@ func (fd *FD) parkUntil(mode uint8, deadline time.Time) error { return nil } +// WaitWrite waits until data can be written to fd. +func (fd *FD) WaitWrite() error { + if fd.wDeadline.IsZero() { + wait(fd.Sysfd, pollModeWrite) + return nil + } + return fd.parkUntil(pollModeWrite, fd.wDeadline) +} + // byteCount turns the -1 a failed read or write returns into 0, as Go's // internal/poll does in fd_unix.go. func byteCount(n int, err error) (int, error) { diff --git a/src/runtime/arch_tinygowasm.go b/src/runtime/arch_tinygowasm.go index cfb8dfef64..6089118303 100644 --- a/src/runtime/arch_tinygowasm.go +++ b/src/runtime/arch_tinygowasm.go @@ -91,16 +91,17 @@ func saveStackPointer() { // 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. + // Grow memory by up to 64 MiB at a time and back off if the host caps memory. + // Cap total pages at 65535 so uintptr(pages) * wasmPageSize does not wrap to 0. + const maxPages = 65535 memorySize := wasm_memory_size(wasmMemoryIndex) - result := wasm_memory_grow(wasmMemoryIndex, memorySize) - if result == -1 { - // Grow failed. - return false + growPages := min(memorySize, 1024, maxPages-memorySize) + for growPages > 0 { + if wasm_memory_grow(wasmMemoryIndex, growPages) != -1 { + setHeapEnd(uintptr(wasm_memory_size(wasmMemoryIndex)) * wasmPageSize) + return true + } + growPages /= 2 } - - setHeapEnd(uintptr(wasm_memory_size(wasmMemoryIndex) * wasmPageSize)) - - // Heap has grown successfully. - return true + return false } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 631308a63e..53754ea1c0 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -176,7 +176,7 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, typeInfo unsafe.P // it. This is used for the clear builtin, and can be used to reuse a map (to // avoid extra heap allocations). func hashmapClear(m *hashmap) { - if m == nil { + if m == nil || m.count == 0 { // Nothing to do. According to the spec: // > If the map or slice is nil, clear is a no-op. return @@ -415,15 +415,19 @@ func hashmapCopy(m *hashmap, sizeBits uint8) hashmap { bucketBufSize := hashmapBucketSize(m) n.buckets = alloc(bucketBufSize*numBuckets, hashmapType(m).bucketLayout) - // use a hashmap iterator to go through the old map - var it hashmapIterator - - var key = alloc(m.keySize, hashmapType(m).keyLayout) - var value = alloc(m.valueSize, hashmapType(m).valueLayout) - - for hashmapNext(m, &it, key, value) { - h := n.keyHash(key, uintptr(n.keySize), n.seed) - hashmapSet(&n, key, value, h) + oldNumBuckets := uintptr(1) << m.bucketBits + for i := uintptr(0); i < oldNumBuckets; i++ { + for b := hashmapBucketAddr(m, m.buckets, i); b != nil; b = b.next { + for j := uint8(0); j < 8; j++ { + if b.tophash[j] == 0 { + continue + } + key := hashmapSlotKeyData(m, hashmapSlotKey(m, b, j)) + value := hashmapSlotValueData(m, hashmapSlotValue(m, b, j)) + h := n.keyHash(key, uintptr(n.keySize), n.seed) + hashmapSet(&n, key, value, h) + } + } } return n diff --git a/src/runtime/slice.go b/src/runtime/slice.go index 5206634fd2..f5d954940e 100644 --- a/src/runtime/slice.go +++ b/src/runtime/slice.go @@ -29,12 +29,20 @@ func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr, // No need to grow, return the input slice. return oldBuf, oldLen, oldCap } - - // This can be made more memory-efficient by multiplying by some other constant, such as 1.5, - // which seems to be allowed by the Go language specification (but this can be observed by - // programs); however, due to memory fragmentation and the current state of the TinyGo - // memory allocators, this causes some difficult to debug issues. - newCap = 1 << bits.Len(uint(newCap)) + if elemSize == 0 { + return oldBuf, oldLen, newCap + } + if oldCap < 256 { + newCap = 1 << bits.Len(uint(newCap)) + } else { + growCap := oldCap + for 0 < growCap && growCap < newCap { + growCap += (growCap + 3*256) / 4 + } + if growCap > 0 { + newCap = growCap + } + } buf := alloc(newCap*elemSize, layout) if oldLen > 0 {