Skip to content

Commit 3612863

Browse files
runtime: reused freed memory blocks on wasm
When compiling Go programs to WebAssembly, the memory allocation strategy was neither releasing memory to the OS nor reusing blocks freed by calls to `runtime.sysFreeOS`. This CL unifies the plan9 and wasm memory management strategy since both platforms use a linear memory space and do not have a mechanism for returning memory blocks to the OS. Fixes #59061
1 parent 70308d1 commit 3612863

File tree

4 files changed

+194
-238
lines changed

4 files changed

+194
-238
lines changed

src/runtime/mem_js.go

+9-63
Original file line numberDiff line numberDiff line change
@@ -6,72 +6,21 @@
66

77
package runtime
88

9-
import (
10-
"unsafe"
11-
)
9+
import "unsafe"
1210

13-
// Don't split the stack as this function may be invoked without a valid G,
14-
// which prevents us from allocating more stack.
15-
//
16-
//go:nosplit
17-
func sysAllocOS(n uintptr) unsafe.Pointer {
18-
p := sysReserveOS(nil, n)
19-
sysMapOS(p, n)
20-
return p
21-
}
22-
23-
func sysUnusedOS(v unsafe.Pointer, n uintptr) {
24-
}
25-
26-
func sysUsedOS(v unsafe.Pointer, n uintptr) {
27-
}
28-
29-
func sysHugePageOS(v unsafe.Pointer, n uintptr) {
30-
}
31-
32-
// Don't split the stack as this function may be invoked without a valid G,
33-
// which prevents us from allocating more stack.
34-
//
35-
//go:nosplit
36-
func sysFreeOS(v unsafe.Pointer, n uintptr) {
37-
}
38-
39-
func sysFaultOS(v unsafe.Pointer, n uintptr) {
40-
}
11+
// https://webassembly.github.io/spec/core/exec/runtime.html#memory-instances
12+
const _PAGESIZE = 64 * 1024
4113

42-
var reserveEnd uintptr
14+
func sbrk(n uintptr) unsafe.Pointer {
15+
grow := (int32(n) + _PAGESIZE - 1) / _PAGESIZE
16+
size := currentMemory()
4317

44-
func sysReserveOS(v unsafe.Pointer, n uintptr) unsafe.Pointer {
45-
// TODO(neelance): maybe unify with mem_plan9.go, depending on how https://github.com/WebAssembly/design/blob/master/FutureFeatures.md#finer-grained-control-over-memory turns out
46-
47-
if v != nil {
48-
// The address space of WebAssembly's linear memory is contiguous,
49-
// so requesting specific addresses is not supported. We could use
50-
// a different address, but then mheap.sysAlloc discards the result
51-
// right away and we don't reuse chunks passed to sysFree.
18+
if growMemory(grow) < 0 {
5219
return nil
5320
}
5421

55-
// Round up the initial reserveEnd to 64 KiB so that
56-
// reservations are always aligned to the page size.
57-
initReserveEnd := alignUp(lastmoduledatap.end, physPageSize)
58-
if reserveEnd < initReserveEnd {
59-
reserveEnd = initReserveEnd
60-
}
61-
v = unsafe.Pointer(reserveEnd)
62-
reserveEnd += alignUp(n, physPageSize)
63-
64-
current := currentMemory()
65-
// reserveEnd is always at a page boundary.
66-
needed := int32(reserveEnd / physPageSize)
67-
if current < needed {
68-
if growMemory(needed-current) == -1 {
69-
return nil
70-
}
71-
resetMemoryDataView()
72-
}
73-
74-
return v
22+
resetMemoryDataView()
23+
return unsafe.Pointer(uintptr(size) * _PAGESIZE)
7524
}
7625

7726
func currentMemory() int32
@@ -82,6 +31,3 @@ func growMemory(pages int32) int32
8231
//
8332
//go:wasmimport gojs runtime.resetMemoryDataView
8433
func resetMemoryDataView()
85-
86-
func sysMapOS(v unsafe.Pointer, n uintptr) {
87-
}

src/runtime/mem_plan9.go

-174
Original file line numberDiff line numberDiff line change
@@ -6,126 +6,6 @@ package runtime
66

77
import "unsafe"
88

9-
const memDebug = false
10-
11-
var bloc uintptr
12-
var blocMax uintptr
13-
var memlock mutex
14-
15-
type memHdr struct {
16-
next memHdrPtr
17-
size uintptr
18-
}
19-
20-
var memFreelist memHdrPtr // sorted in ascending order
21-
22-
type memHdrPtr uintptr
23-
24-
func (p memHdrPtr) ptr() *memHdr { return (*memHdr)(unsafe.Pointer(p)) }
25-
func (p *memHdrPtr) set(x *memHdr) { *p = memHdrPtr(unsafe.Pointer(x)) }
26-
27-
func memAlloc(n uintptr) unsafe.Pointer {
28-
n = memRound(n)
29-
var prevp *memHdr
30-
for p := memFreelist.ptr(); p != nil; p = p.next.ptr() {
31-
if p.size >= n {
32-
if p.size == n {
33-
if prevp != nil {
34-
prevp.next = p.next
35-
} else {
36-
memFreelist = p.next
37-
}
38-
} else {
39-
p.size -= n
40-
p = (*memHdr)(add(unsafe.Pointer(p), p.size))
41-
}
42-
*p = memHdr{}
43-
return unsafe.Pointer(p)
44-
}
45-
prevp = p
46-
}
47-
return sbrk(n)
48-
}
49-
50-
func memFree(ap unsafe.Pointer, n uintptr) {
51-
n = memRound(n)
52-
memclrNoHeapPointers(ap, n)
53-
bp := (*memHdr)(ap)
54-
bp.size = n
55-
bpn := uintptr(ap)
56-
if memFreelist == 0 {
57-
bp.next = 0
58-
memFreelist.set(bp)
59-
return
60-
}
61-
p := memFreelist.ptr()
62-
if bpn < uintptr(unsafe.Pointer(p)) {
63-
memFreelist.set(bp)
64-
if bpn+bp.size == uintptr(unsafe.Pointer(p)) {
65-
bp.size += p.size
66-
bp.next = p.next
67-
*p = memHdr{}
68-
} else {
69-
bp.next.set(p)
70-
}
71-
return
72-
}
73-
for ; p.next != 0; p = p.next.ptr() {
74-
if bpn > uintptr(unsafe.Pointer(p)) && bpn < uintptr(unsafe.Pointer(p.next)) {
75-
break
76-
}
77-
}
78-
if bpn+bp.size == uintptr(unsafe.Pointer(p.next)) {
79-
bp.size += p.next.ptr().size
80-
bp.next = p.next.ptr().next
81-
*p.next.ptr() = memHdr{}
82-
} else {
83-
bp.next = p.next
84-
}
85-
if uintptr(unsafe.Pointer(p))+p.size == bpn {
86-
p.size += bp.size
87-
p.next = bp.next
88-
*bp = memHdr{}
89-
} else {
90-
p.next.set(bp)
91-
}
92-
}
93-
94-
func memCheck() {
95-
if !memDebug {
96-
return
97-
}
98-
for p := memFreelist.ptr(); p != nil && p.next != 0; p = p.next.ptr() {
99-
if uintptr(unsafe.Pointer(p)) == uintptr(unsafe.Pointer(p.next)) {
100-
print("runtime: ", unsafe.Pointer(p), " == ", unsafe.Pointer(p.next), "\n")
101-
throw("mem: infinite loop")
102-
}
103-
if uintptr(unsafe.Pointer(p)) > uintptr(unsafe.Pointer(p.next)) {
104-
print("runtime: ", unsafe.Pointer(p), " > ", unsafe.Pointer(p.next), "\n")
105-
throw("mem: unordered list")
106-
}
107-
if uintptr(unsafe.Pointer(p))+p.size > uintptr(unsafe.Pointer(p.next)) {
108-
print("runtime: ", unsafe.Pointer(p), "+", p.size, " > ", unsafe.Pointer(p.next), "\n")
109-
throw("mem: overlapping blocks")
110-
}
111-
for b := add(unsafe.Pointer(p), unsafe.Sizeof(memHdr{})); uintptr(b) < uintptr(unsafe.Pointer(p))+p.size; b = add(b, 1) {
112-
if *(*byte)(b) != 0 {
113-
print("runtime: value at addr ", b, " with offset ", uintptr(b)-uintptr(unsafe.Pointer(p)), " in block ", p, " of size ", p.size, " is not zero\n")
114-
throw("mem: uninitialised memory")
115-
}
116-
}
117-
}
118-
}
119-
120-
func memRound(p uintptr) uintptr {
121-
return (p + _PAGESIZE - 1) &^ (_PAGESIZE - 1)
122-
}
123-
124-
func initBloc() {
125-
bloc = memRound(firstmoduledata.end)
126-
blocMax = bloc
127-
}
128-
1299
func sbrk(n uintptr) unsafe.Pointer {
13010
// Plan 9 sbrk from /sys/src/libc/9sys/sbrk.c
13111
bl := bloc
@@ -139,57 +19,3 @@ func sbrk(n uintptr) unsafe.Pointer {
13919
bloc += n
14020
return unsafe.Pointer(bl)
14121
}
142-
143-
func sysAllocOS(n uintptr) unsafe.Pointer {
144-
lock(&memlock)
145-
p := memAlloc(n)
146-
memCheck()
147-
unlock(&memlock)
148-
return p
149-
}
150-
151-
func sysFreeOS(v unsafe.Pointer, n uintptr) {
152-
lock(&memlock)
153-
if uintptr(v)+n == bloc {
154-
// Address range being freed is at the end of memory,
155-
// so record a new lower value for end of memory.
156-
// Can't actually shrink address space because segment is shared.
157-
memclrNoHeapPointers(v, n)
158-
bloc -= n
159-
} else {
160-
memFree(v, n)
161-
memCheck()
162-
}
163-
unlock(&memlock)
164-
}
165-
166-
func sysUnusedOS(v unsafe.Pointer, n uintptr) {
167-
}
168-
169-
func sysUsedOS(v unsafe.Pointer, n uintptr) {
170-
}
171-
172-
func sysHugePageOS(v unsafe.Pointer, n uintptr) {
173-
}
174-
175-
func sysMapOS(v unsafe.Pointer, n uintptr) {
176-
}
177-
178-
func sysFaultOS(v unsafe.Pointer, n uintptr) {
179-
}
180-
181-
func sysReserveOS(v unsafe.Pointer, n uintptr) unsafe.Pointer {
182-
lock(&memlock)
183-
var p unsafe.Pointer
184-
if uintptr(v) == bloc {
185-
// Address hint is the current end of memory,
186-
// so try to extend the address space.
187-
p = sbrk(n)
188-
}
189-
if p == nil && v == nil {
190-
p = memAlloc(n)
191-
memCheck()
192-
}
193-
unlock(&memlock)
194-
return p
195-
}

0 commit comments

Comments
 (0)