Skip to content

Commit 1c485be

Browse files
runtime: reuse 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 2449bbb commit 1c485be

File tree

5 files changed

+194
-240
lines changed

5 files changed

+194
-240
lines changed

src/runtime/mem_js.go

+7-64
Original file line numberDiff line numberDiff line change
@@ -6,72 +6,18 @@
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-
}
11+
func sbrk(n uintptr) unsafe.Pointer {
12+
grow := divRoundUp(n, physPageSize)
13+
size := currentMemory()
3814

39-
func sysFaultOS(v unsafe.Pointer, n uintptr) {
40-
}
41-
42-
var reserveEnd uintptr
43-
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.
15+
if growMemory(int32(grow)) < 0 {
5216
return nil
5317
}
5418

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
19+
resetMemoryDataView()
20+
return unsafe.Pointer(uintptr(size) * physPageSize)
7521
}
7622

7723
func currentMemory() int32
@@ -82,6 +28,3 @@ func growMemory(pages int32) int32
8228
//
8329
//go:wasmimport gojs runtime.resetMemoryDataView
8430
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)