Skip to content

Commit fbc65de

Browse files
committed
runtime: reduce typed Boehm allocation overhead
Canonicalize all-pointer layouts so Boehm can use conservative allocation when it is exact. Bypass trivial allocation wrappers and skip typed-array setup for scalar objects.
1 parent 727fd32 commit fbc65de

3 files changed

Lines changed: 49 additions & 22 deletions

File tree

compiler/llvm.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,30 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
252252
// Create the pointer bitmap.
253253
objectSizeBytes := c.targetData.TypeAllocSize(t)
254254
pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.dataPtrType))
255+
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
256+
pointerBits := pointerSize * 8
255257
bitmapLen := objectSizeBytes / pointerAlignment
256258
bitmapBytes := (bitmapLen + 7) / 8
257259
bitmap := make([]byte, bitmapBytes, max(bitmapBytes, 8))
258260
c.buildPointerBitmap(bitmap, pointerAlignment, pos, t, 0)
259261

262+
// Use the one-pointer layout when every pointer-sized word is a pointer.
263+
// Repeating this layout is equivalent for objects of any size.
264+
pointerUnits := pointerSize / pointerAlignment
265+
allPointers := bitmapLen != 0 && bitmapLen%pointerUnits == 0
266+
for i := uint64(0); allPointers && i < bitmapLen; i++ {
267+
isPointer := bitmap[i/8]&(1<<(i%8)) != 0
268+
if isPointer != (i%pointerUnits == 0) {
269+
allPointers = false
270+
}
271+
}
272+
if allPointers {
273+
layout := (pointerBits + pointerUnits) << 1
274+
layout |= 1
275+
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
276+
}
277+
260278
// Try to encode the layout inline.
261-
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
262-
pointerBits := pointerSize * 8
263279
if bitmapLen < pointerBits {
264280
rawMask := binary.LittleEndian.Uint64(bitmap[0:8])
265281
layout := rawMask*pointerBits + bitmapLen

compiler/testdata/gc.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2
100100
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
101101
entry:
102102
%stackalloc = alloca i8, align 1
103-
%new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3
103+
%new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3
104104
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3
105105
ret ptr %new
106106
}

src/runtime/gc_boehm.go

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import (
2727
const needsStaticHeap = false
2828

2929
const boehmLayoutSizeBits = 4 + unsafe.Sizeof(uintptr(0))/4
30+
const (
31+
boehmPtrFreeKind = 0
32+
boehmNormalKind = 1
33+
)
3034

3135
var gcLock task.PMutex
3236

@@ -77,35 +81,43 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
7781
// This object is entirely pointer free, for example make([]int, ...).
7882
// Make sure the GC knows this so it doesn't scan the object
7983
// unnecessarily to improve performance.
80-
ptr = libgc_malloc_atomic(size)
81-
// Memory returned from libgc_malloc_atomic has not been zeroed so we
82-
// have to do that manually.
84+
ptr = libgc_malloc_kind(size, boehmPtrFreeKind)
8385
needsZero = true
8486
case gclayout.Conservative.AsPtr():
8587
// Stack storage does not have an ordinary repeating Go object layout.
86-
ptr = libgc_malloc(size)
88+
ptr = libgc_malloc_kind(size, boehmNormalKind)
89+
case gclayout.Pointer.AsPtr(), gclayout.PointerPair.AsPtr():
90+
// Conservative scanning is exact when every word is a pointer.
91+
ptr = libgc_malloc_kind(size, boehmNormalKind)
8792
default:
8893
elementWords := boehmLayoutElementWords(layout)
89-
pointerSize := unsafe.Sizeof(uintptr(0))
90-
if elementWords == 0 || elementWords > size/pointerSize {
94+
pointerAlign := unsafe.Alignof(uintptr(0))
95+
if elementWords == 0 || elementWords > size/pointerAlign {
9196
// This should not happen for compiler-generated Go allocations.
92-
ptr = libgc_malloc(size)
97+
ptr = libgc_malloc_kind(size, boehmNormalKind)
9398
break
9499
}
95-
elementSize := elementWords * pointerSize
100+
elementSize := elementWords * pointerAlign
96101
if size%elementSize != 0 {
97-
ptr = libgc_malloc(size)
102+
ptr = libgc_malloc_kind(size, boehmNormalKind)
98103
break
99104
}
100105

101106
descriptor := libgc_make_descriptor(uintptr(layout))
102107
if descriptor == 0 {
103-
// Descriptor construction can fail under memory pressure. A
104-
// conservative allocation remains correct in that case.
105-
ptr = libgc_malloc(size)
108+
// The bridge returns zero if its cache or bitmap allocation fails.
109+
// It also rejects the no-pointer descriptor, which is handled above.
110+
ptr = libgc_malloc_kind(size, boehmNormalKind)
106111
break
107112
}
108-
ptr = libgc_calloc_explicitly_typed(size/elementSize, elementSize, descriptor)
113+
elementCount := size / elementSize
114+
if elementCount == 1 {
115+
ptr = libgc_malloc_explicitly_typed(size, descriptor)
116+
} else {
117+
ptr = libgc_calloc_explicitly_typed(
118+
elementCount, elementSize, descriptor,
119+
)
120+
}
109121
}
110122
gcResumeWorld()
111123
gcLock.Unlock()
@@ -116,7 +128,6 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
116128
if needsZero {
117129
memzero(ptr, size)
118130
}
119-
120131
return ptr
121132
}
122133

@@ -202,11 +213,8 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
202213
//export GC_init
203214
func libgc_init()
204215

205-
//export GC_malloc
206-
func libgc_malloc(uintptr) unsafe.Pointer
207-
208-
//export GC_malloc_atomic
209-
func libgc_malloc_atomic(uintptr) unsafe.Pointer
216+
//export GC_malloc_kind
217+
func libgc_malloc_kind(uintptr, int32) unsafe.Pointer
210218

211219
//export GC_malloc_atomic_uncollectable
212220
func libgc_malloc_atomic_uncollectable(uintptr) unsafe.Pointer
@@ -217,6 +225,9 @@ func libgc_make_descriptor(uintptr) uintptr
217225
//export GC_calloc_explicitly_typed
218226
func libgc_calloc_explicitly_typed(uintptr, uintptr, uintptr) unsafe.Pointer
219227

228+
//export GC_malloc_explicitly_typed
229+
func libgc_malloc_explicitly_typed(uintptr, uintptr) unsafe.Pointer
230+
220231
//export GC_free
221232
func libgc_free(unsafe.Pointer)
222233

0 commit comments

Comments
 (0)