Skip to content

Commit 5af4c07

Browse files
aykevldeadprogram
authored andcommitted
runtime: put metadata at the top end of the heap
This commit swaps the layout of the heap. Previously, the metadata was at the start and the data blocks (the actual heap memory) followed after. This commit swaps those, so that the heap area starts with the data blocks followed by the heap metadata. This arrangement is not very relevant for baremetal targets that always have all RAM allocated, but it is an important improvement for other targets such as WebAssembly where growing the heap is possible but starting with a small heap is a good idea. Because the metadata lives at the end, and because the metadata does not contain pointers, it can easily be moved. The data itself cannot be moved as the conservative GC does not know all the pointer locations, plus moving the data could be very expensive.
1 parent 154c7c6 commit 5af4c07

File tree

1 file changed

+19
-20
lines changed

1 file changed

+19
-20
lines changed

src/runtime/gc_conservative.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ package runtime
1919
// "head" and is followed by "tail" blocks. The reason for this distinction is
2020
// that this way, the start and end of every object can be found easily.
2121
//
22-
// Metadata is stored in a special area at the beginning of the heap, in the
23-
// area heapStart..poolStart. The actual blocks are stored in
24-
// poolStart..heapEnd.
22+
// Metadata is stored in a special area at the end of the heap, in the area
23+
// metadataStart..heapEnd. The actual blocks are stored in
24+
// heapStart..metadataStart.
2525
//
2626
// More information:
2727
// https://github.com/micropython/micropython/wiki/Memory-Manager
@@ -51,9 +51,9 @@ const (
5151
)
5252

5353
var (
54-
poolStart uintptr // the first heap pointer
55-
nextAlloc gcBlock // the next block that should be tried by the allocator
56-
endBlock gcBlock // the block just past the end of the available space
54+
metadataStart unsafe.Pointer // pointer to the start of the heap
55+
nextAlloc gcBlock // the next block that should be tried by the allocator
56+
endBlock gcBlock // the block just past the end of the available space
5757
)
5858

5959
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
@@ -96,10 +96,10 @@ type gcBlock uintptr
9696
// blockFromAddr returns a block given an address somewhere in the heap (which
9797
// might not be heap-aligned).
9898
func blockFromAddr(addr uintptr) gcBlock {
99-
if gcAsserts && (addr < poolStart || addr >= heapEnd) {
99+
if gcAsserts && (addr < heapStart || addr >= uintptr(metadataStart)) {
100100
runtimePanic("gc: trying to get block from invalid address")
101101
}
102-
return gcBlock((addr - poolStart) / bytesPerBlock)
102+
return gcBlock((addr - heapStart) / bytesPerBlock)
103103
}
104104

105105
// Return a pointer to the start of the allocated object.
@@ -109,7 +109,7 @@ func (b gcBlock) pointer() unsafe.Pointer {
109109

110110
// Return the address of the start of the allocated object.
111111
func (b gcBlock) address() uintptr {
112-
return poolStart + uintptr(b)*bytesPerBlock
112+
return heapStart + uintptr(b)*bytesPerBlock
113113
}
114114

115115
// findHead returns the head (first block) of an object, assuming the block
@@ -141,15 +141,15 @@ func (b gcBlock) findNext() gcBlock {
141141

142142
// State returns the current block state.
143143
func (b gcBlock) state() blockState {
144-
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
144+
stateBytePtr := (*uint8)(unsafe.Pointer(uintptr(metadataStart) + uintptr(b/blocksPerStateByte)))
145145
return blockState(*stateBytePtr>>((b%blocksPerStateByte)*2)) % 4
146146
}
147147

148148
// setState sets the current block to the given state, which must contain more
149149
// bits than the current state. Allowed transitions: from free to any state and
150150
// from head to mark.
151151
func (b gcBlock) setState(newState blockState) {
152-
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
152+
stateBytePtr := (*uint8)(unsafe.Pointer(uintptr(metadataStart) + uintptr(b/blocksPerStateByte)))
153153
*stateBytePtr |= uint8(newState << ((b % blocksPerStateByte) * 2))
154154
if gcAsserts && b.state() != newState {
155155
runtimePanic("gc: setState() was not successful")
@@ -158,7 +158,7 @@ func (b gcBlock) setState(newState blockState) {
158158

159159
// markFree sets the block state to free, no matter what state it was in before.
160160
func (b gcBlock) markFree() {
161-
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
161+
stateBytePtr := (*uint8)(unsafe.Pointer(uintptr(metadataStart) + uintptr(b/blocksPerStateByte)))
162162
*stateBytePtr &^= uint8(blockStateMask << ((b % blocksPerStateByte) * 2))
163163
if gcAsserts && b.state() != blockStateFree {
164164
runtimePanic("gc: markFree() was not successful")
@@ -172,7 +172,7 @@ func (b gcBlock) unmark() {
172172
runtimePanic("gc: unmark() on a block that is not marked")
173173
}
174174
clearMask := blockStateMask ^ blockStateHead // the bits to clear from the state
175-
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
175+
stateBytePtr := (*uint8)(unsafe.Pointer(uintptr(metadataStart) + uintptr(b/blocksPerStateByte)))
176176
*stateBytePtr &^= uint8(clearMask << ((b % blocksPerStateByte) * 2))
177177
if gcAsserts && b.state() != blockStateHead {
178178
runtimePanic("gc: unmark() was not successful")
@@ -188,18 +188,17 @@ func initHeap() {
188188

189189
// Allocate some memory to keep 2 bits of information about every block.
190190
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
191+
metadataStart = unsafe.Pointer(heapEnd - metadataSize)
191192

192-
// Align the pool.
193-
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
194-
poolEnd := heapEnd &^ (bytesPerBlock - 1)
195-
numBlocks := (poolEnd - poolStart) / bytesPerBlock
193+
// Use the rest of the available memory as heap.
194+
numBlocks := (uintptr(metadataStart) - heapStart) / bytesPerBlock
196195
endBlock = gcBlock(numBlocks)
197196
if gcDebug {
198197
println("heapStart: ", heapStart)
199198
println("heapEnd: ", heapEnd)
200199
println("total size: ", totalSize)
201200
println("metadata size: ", metadataSize)
202-
println("poolStart: ", poolStart)
201+
println("metadataStart: ", metadataStart)
203202
println("# of blocks: ", numBlocks)
204203
println("# of block states:", metadataSize*blocksPerStateByte)
205204
}
@@ -209,7 +208,7 @@ func initHeap() {
209208
}
210209

211210
// Set all block states to 'free'.
212-
memzero(unsafe.Pointer(heapStart), metadataSize)
211+
memzero(metadataStart, metadataSize)
213212
}
214213

215214
// alloc tries to find some free space on the heap, possibly doing a garbage
@@ -496,7 +495,7 @@ func sweep() {
496495
// simply returns whether it lies anywhere in the heap. Go allows interior
497496
// pointers so we can't check alignment or anything like that.
498497
func looksLikePointer(ptr uintptr) bool {
499-
return ptr >= poolStart && ptr < heapEnd
498+
return ptr >= heapStart && ptr < uintptr(metadataStart)
500499
}
501500

502501
// dumpHeap can be used for debugging purposes. It dumps the state of each heap

0 commit comments

Comments
 (0)