--- /dev/null
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.arenas
+
+/*
+The arena package provides the ability to allocate memory for a collection
+of Go values and free that space manually all at once, safely. The purpose
+of this functionality is to improve efficiency: manually freeing memory
+before a garbage collection delays that cycle. Less frequent cycles means
+the CPU cost of the garbage collector is incurred less frequently.
+
+This functionality in this package is mostly captured in the Arena type.
+Arenas allocate large chunks of memory for Go values, so they're likely to
+be inefficient for allocating only small amounts of small Go values. They're
+best used in bulk, on the order of MiB of memory allocated on each use.
+
+Note that by allowing for this limited form of manual memory allocation
+that use-after-free bugs are possible with regular Go values. This package
+limits the impact of these use-after-free bugs by preventing reuse of freed
+memory regions until the garbage collector is able to determine that it is
+safe. Typically, a use-after-free bug will result in a fault and a helpful
+error message, but this package reserves the right to not force a fault on
+freed memory. That means a valid implementation of this package is to just
+allocate all memory the way the runtime normally would, and in fact, it
+reserves the right to occasionally do so for some Go values.
+*/
+package arena
+
+import (
+ "internal/reflectlite"
+ "unsafe"
+)
+
+// Arena represents a collection of Go values allocated and freed together.
+// Arenas are useful for improving efficiency as they may be freed back to
+// the runtime manually, though any memory obtained from freed arenas must
+// not be accessed once that happens. An Arena is automatically freed once
+// it is no longer referenced, so it must be kept alive (see runtime.KeepAlive)
+// until any memory allocated from it is no longer needed.
+//
+// An Arena must never be used concurrently by multiple goroutines.
+type Arena struct {
+ a unsafe.Pointer
+}
+
+// NewArena allocates a new arena.
+func NewArena() *Arena {
+ return &Arena{a: runtime_arena_newArena()}
+}
+
+// Free frees the arena (and all objects allocated from the arena) so that
+// memory backing the arena can be reused fairly quickly without garbage
+// collection overhead. Applications must not call any method on this
+// arena after it has been freed.
+func (a *Arena) Free() {
+ runtime_arena_arena_Free(a.a)
+ a.a = nil
+}
+
+// New creates a new *T in the provided arena. The *T must not be used after
+// the arena is freed. Accessing the value after free may result in a fault,
+// but this fault is also not guaranteed.
+func New[T any](a *Arena) *T {
+ return runtime_arena_arena_New(a.a, reflectlite.TypeOf((*T)(nil))).(*T)
+}
+
+// MakeSlice creates a new []T with the provided capacity and length. The []T must
+// not be used after the arena is freed. Accessing the underlying storage of the
+// slice after free may result in a fault, but this fault is also not guaranteed.
+func MakeSlice[T any](a *Arena, len, cap int) []T {
+ var sl []T
+ runtime_arena_arena_Slice(a.a, &sl, cap)
+ return sl[:len]
+}
+
+// Clone makes a shallow copy of the input value that is no longer bound to any
+// arena it may have been allocated from, returning the copy. If it was not
+// allocated from an arena, it is returned untouched. This function is useful
+// to more easily let an arena-allocated value out-live its arena.
+// T must be a pointer, a slice, or a string, otherwise this function will panic.
+func Clone[T any](s T) T {
+ return runtime_arena_heapify(s).(T)
+}
+
+//go:linkname reflect_arena_New reflect.arena_New
+func reflect_arena_New(a *Arena, typ any) any {
+ return runtime_arena_arena_New(a.a, typ)
+}
+
+//go:linkname runtime_arena_newArena
+func runtime_arena_newArena() unsafe.Pointer
+
+//go:linkname runtime_arena_arena_New
+func runtime_arena_arena_New(arena unsafe.Pointer, typ any) any
+
+// Mark as noescape to avoid escaping the slice header.
+//
+//go:noescape
+//go:linkname runtime_arena_arena_Slice
+func runtime_arena_arena_Slice(arena unsafe.Pointer, slice any, cap int)
+
+//go:linkname runtime_arena_arena_Free
+func runtime_arena_arena_Free(arena unsafe.Pointer)
+
+//go:linkname runtime_arena_heapify
+func runtime_arena_heapify(any) any
--- /dev/null
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.arenas
+
+package arena_test
+
+import (
+ "arena"
+ "testing"
+)
+
+type T1 struct {
+ n int
+}
+type T2 [1 << 20]byte // 1MiB
+
+func TestSmoke(t *testing.T) {
+ a := arena.NewArena()
+ defer a.Free()
+
+ tt := arena.New[T1](a)
+ tt.n = 1
+
+ ts := arena.MakeSlice[T1](a, 99, 100)
+ if len(ts) != 99 {
+ t.Errorf("Slice() len = %d, want 99", len(ts))
+ }
+ if cap(ts) != 100 {
+ t.Errorf("Slice() cap = %d, want 100", cap(ts))
+ }
+ ts[1].n = 42
+}
+
+func TestSmokeLarge(t *testing.T) {
+ a := arena.NewArena()
+ defer a.Free()
+ for i := 0; i < 10*64; i++ {
+ _ = arena.New[T2](a)
+ }
+}
RUNTIME
< io;
+ RUNTIME
+ < arena;
+
syscall !< io;
reflect !< sort;
# FMT is OS (which includes string routines) plus reflect and fmt.
# It does not include package log, which should be avoided in core packages.
- strconv, unicode
+ arena, strconv, unicode
< reflect;
os, reflect
--- /dev/null
+// Code generated by mkconsts.go. DO NOT EDIT.
+
+//go:build !goexperiment.arenas
+// +build !goexperiment.arenas
+
+package goexperiment
+
+const Arenas = false
+const ArenasInt = 0
--- /dev/null
+// Code generated by mkconsts.go. DO NOT EDIT.
+
+//go:build goexperiment.arenas
+// +build goexperiment.arenas
+
+package goexperiment
+
+const Arenas = true
+const ArenasInt = 1
// CoverageRedesign enables the new compiler-based code coverage
// tooling.
CoverageRedesign bool
+
+ // Arenas causes the "arena" standard library package to be visible
+ // to the outside world.
+ Arenas bool
}
--- /dev/null
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.arenas
+
+package reflect
+
+import "arena"
+
+// ArenaNew returns a Value representing a pointer to a new zero value for the
+// specified type, allocating storage for it in the provided arena. That is,
+// the returned Value's Type is PointerTo(typ).
+func ArenaNew(a *arena.Arena, typ Type) Value {
+ return ValueOf(arena_New(a, typ))
+}
+
+func arena_New(a *arena.Arena, typ any) any
"unsafe"
)
+// Functions starting with arena_ are meant to be exported to downstream users
+// of arenas. They should wrap these functions in a higher-lever API.
+//
+// The underlying arena and its resources are managed through an opaque unsafe.Pointer.
+
+// arena_newArena is a wrapper around newUserArena.
+//
+//go:linkname arena_newArena arena.runtime_arena_newArena
+func arena_newArena() unsafe.Pointer {
+ return unsafe.Pointer(newUserArena())
+}
+
+// arena_arena_New is a wrapper around (*userArena).new, except that typ
+// is an any (must be a *_type, still) and typ must be a type descriptor
+// for a pointer to the type to actually be allocated, i.e. pass a *T
+// to allocate a T. This is necessary because this function returns a *T.
+//
+//go:linkname arena_arena_New arena.runtime_arena_arena_New
+func arena_arena_New(arena unsafe.Pointer, typ any) any {
+ t := (*_type)(efaceOf(&typ).data)
+ if t.kind&kindMask != kindPtr {
+ throw("arena_New: non-pointer type")
+ }
+ te := (*ptrtype)(unsafe.Pointer(t)).elem
+ x := ((*userArena)(arena)).new(te)
+ var result any
+ e := efaceOf(&result)
+ e._type = t
+ e.data = x
+ return result
+}
+
+// arena_arena_Slice is a wrapper around (*userArena).slice.
+//
+//go:linkname arena_arena_Slice arena.runtime_arena_arena_Slice
+func arena_arena_Slice(arena unsafe.Pointer, slice any, cap int) {
+ ((*userArena)(arena)).slice(slice, cap)
+}
+
+// arena_arena_Free is a wrapper around (*userArena).free.
+//
+//go:linkname arena_arena_Free arena.runtime_arena_arena_Free
+func arena_arena_Free(arena unsafe.Pointer) {
+ ((*userArena)(arena)).free()
+}
+
+// arena_heapify takes a value that lives in an arena and makes a copy
+// of it on the heap. Values that don't live in an arena are returned unmodified.
+//
+//go:linkname arena_heapify arena.runtime_arena_heapify
+func arena_heapify(s any) any {
+ var v unsafe.Pointer
+ e := efaceOf(&s)
+ t := e._type
+ switch t.kind & kindMask {
+ case kindString:
+ v = stringStructOf((*string)(e.data)).str
+ case kindSlice:
+ v = (*slice)(e.data).array
+ case kindPtr:
+ v = e.data
+ default:
+ panic("arena: Clone only supports pointers, slices, and strings")
+ }
+ span := spanOf(uintptr(v))
+ if span == nil || !span.isUserArenaChunk {
+ // Not stored in a user arena chunk.
+ return s
+ }
+ // Heap-allocate storage for a copy.
+ var x any
+ switch t.kind & kindMask {
+ case kindString:
+ s1 := s.(string)
+ s2, b := rawstring(len(s1))
+ copy(b, s1)
+ x = s2
+ case kindSlice:
+ len := (*slice)(e.data).len
+ et := (*slicetype)(unsafe.Pointer(t)).elem
+ sl := new(slice)
+ *sl = slice{makeslicecopy(et, len, len, (*slice)(e.data).array), len, len}
+ xe := efaceOf(&x)
+ xe._type = t
+ xe.data = unsafe.Pointer(sl)
+ case kindPtr:
+ et := (*ptrtype)(unsafe.Pointer(t)).elem
+ e2 := newobject(et)
+ typedmemmove(et, e2, e.data)
+ xe := efaceOf(&x)
+ xe._type = t
+ xe.data = e2
+ }
+ return x
+}
+
const (
// userArenaChunkBytes is the size of a user arena chunk.
userArenaChunkBytesMax = 8 << 20
GC()
GC()
}
+
+func TestUserArenaCloneString(t *testing.T) {
+ a := NewUserArena()
+
+ // A static string (not on heap or arena)
+ var s = "abcdefghij"
+
+ // Create a byte slice in the arena, initialize it with s
+ var b []byte
+ a.Slice(&b, len(s))
+ copy(b, s)
+
+ // Create a string as using the same memory as the byte slice, hence in
+ // the arena. This could be an arena API, but hasn't really been needed
+ // yet.
+ var as string
+ asHeader := (*reflect.StringHeader)(unsafe.Pointer(&as))
+ asHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(&b)).Data
+ asHeader.Len = len(b)
+
+ // Clone should make a copy of as, since it is in the arena.
+ asCopy := UserArenaClone(as)
+ if (*reflect.StringHeader)(unsafe.Pointer(&as)).Data == (*reflect.StringHeader)(unsafe.Pointer(&asCopy)).Data {
+ t.Error("Clone did not make a copy")
+ }
+
+ // Clone should make a copy of subAs, since subAs is just part of as and so is in the arena.
+ subAs := as[1:3]
+ subAsCopy := UserArenaClone(subAs)
+ if (*reflect.StringHeader)(unsafe.Pointer(&subAs)).Data == (*reflect.StringHeader)(unsafe.Pointer(&subAsCopy)).Data {
+ t.Error("Clone did not make a copy")
+ }
+ if len(subAs) != len(subAsCopy) {
+ t.Errorf("Clone made an incorrect copy (bad length): %d -> %d", len(subAs), len(subAsCopy))
+ } else {
+ for i := range subAs {
+ if subAs[i] != subAsCopy[i] {
+ t.Errorf("Clone made an incorrect copy (data at index %d): %d -> %d", i, subAs[i], subAs[i])
+ }
+ }
+ }
+
+ // Clone should not make a copy of doubleAs, since doubleAs will be on the heap.
+ doubleAs := as + as
+ doubleAsCopy := UserArenaClone(doubleAs)
+ if (*reflect.StringHeader)(unsafe.Pointer(&doubleAs)).Data != (*reflect.StringHeader)(unsafe.Pointer(&doubleAsCopy)).Data {
+ t.Error("Clone should not have made a copy")
+ }
+
+ // Clone should not make a copy of s, since s is a static string.
+ sCopy := UserArenaClone(s)
+ if (*reflect.StringHeader)(unsafe.Pointer(&s)).Data != (*reflect.StringHeader)(unsafe.Pointer(&sCopy)).Data {
+ t.Error("Clone should not have made a copy")
+ }
+
+ a.Free()
+}
+
+func TestUserArenaClonePointer(t *testing.T) {
+ a := NewUserArena()
+
+ // Clone should not make a copy of a heap-allocated smallScalar.
+ x := Escape(new(smallScalar))
+ xCopy := UserArenaClone(x)
+ if unsafe.Pointer(x) != unsafe.Pointer(xCopy) {
+ t.Errorf("Clone should not have made a copy: %#v -> %#v", x, xCopy)
+ }
+
+ // Clone should make a copy of an arena-allocated smallScalar.
+ var i any
+ i = (*smallScalar)(nil)
+ a.New(&i)
+ xArena := i.(*smallScalar)
+ xArenaCopy := UserArenaClone(xArena)
+ if unsafe.Pointer(xArena) == unsafe.Pointer(xArenaCopy) {
+ t.Errorf("Clone should have made a copy: %#v -> %#v", xArena, xArenaCopy)
+ }
+ if *xArena != *xArenaCopy {
+ t.Errorf("Clone made an incorrect copy copy: %#v -> %#v", *xArena, *xArenaCopy)
+ }
+
+ a.Free()
+}
+
+func TestUserArenaCloneSlice(t *testing.T) {
+ a := NewUserArena()
+
+ // A static string (not on heap or arena)
+ var s = "klmnopqrstuv"
+
+ // Create a byte slice in the arena, initialize it with s
+ var b []byte
+ a.Slice(&b, len(s))
+ copy(b, s)
+
+ // Clone should make a copy of b, since it is in the arena.
+ bCopy := UserArenaClone(b)
+ if unsafe.Pointer(&b[0]) == unsafe.Pointer(&bCopy[0]) {
+ t.Errorf("Clone did not make a copy: %#v -> %#v", b, bCopy)
+ }
+ if len(b) != len(bCopy) {
+ t.Errorf("Clone made an incorrect copy (bad length): %d -> %d", len(b), len(bCopy))
+ } else {
+ for i := range b {
+ if b[i] != bCopy[i] {
+ t.Errorf("Clone made an incorrect copy (data at index %d): %d -> %d", i, b[i], bCopy[i])
+ }
+ }
+ }
+
+ // Clone should make a copy of bSub, since bSub is just part of b and so is in the arena.
+ bSub := b[1:3]
+ bSubCopy := UserArenaClone(bSub)
+ if unsafe.Pointer(&bSub[0]) == unsafe.Pointer(&bSubCopy[0]) {
+ t.Errorf("Clone did not make a copy: %#v -> %#v", bSub, bSubCopy)
+ }
+ if len(bSub) != len(bSubCopy) {
+ t.Errorf("Clone made an incorrect copy (bad length): %d -> %d", len(bSub), len(bSubCopy))
+ } else {
+ for i := range bSub {
+ if bSub[i] != bSubCopy[i] {
+ t.Errorf("Clone made an incorrect copy (data at index %d): %d -> %d", i, bSub[i], bSubCopy[i])
+ }
+ }
+ }
+
+ // Clone should not make a copy of bNotArena, since it will not be in an arena.
+ bNotArena := make([]byte, len(s))
+ copy(bNotArena, s)
+ bNotArenaCopy := UserArenaClone(bNotArena)
+ if unsafe.Pointer(&bNotArena[0]) != unsafe.Pointer(&bNotArenaCopy[0]) {
+ t.Error("Clone should not have made a copy")
+ }
+
+ a.Free()
+}
+
+func TestUserArenaClonePanic(t *testing.T) {
+ var s string
+ func() {
+ x := smallScalar{2}
+ defer func() {
+ if v := recover(); v != nil {
+ s = v.(string)
+ }
+ }()
+ UserArenaClone(x)
+ }()
+ if s == "" {
+ t.Errorf("expected panic from Clone")
+ }
+}
return n
}
+func UserArenaClone[T any](s T) T {
+ return arena_heapify(s).(T)
+}
+
var AlignUp = alignUp
// BlockUntilEmptyFinalizerQueue blocks until either the finalizer
throw("nil elem type!")
}
+ if inUserArenaChunk(uintptr(e.data)) {
+ // Arena-allocated objects are not eligible for finalizers.
+ throw("runtime.SetFinalizer: first argument was allocated into an arena")
+ }
+
// find the containing object
base, _, _ := findObject(uintptr(e.data), 0, 0)