// Copyright 2020 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. // Package intern lets you make smaller comparable values by boxing // a larger comparable value (such as a 16 byte string header) down // into a globally unique 8 byte pointer. // // The globally unique pointers are garbage collected with weak // references and finalizers. This package hides that. package intern import ( "internal/godebug" "runtime" "sync" "unsafe" ) // A Value pointer is the handle to an underlying comparable value. // See func Get for how Value pointers may be used. type Value struct { _ [0]func() // prevent people from accidentally using value type as comparable cmpVal any // resurrected is guarded by mu (for all instances of Value). // It is set true whenever v is synthesized from a uintptr. resurrected bool } // Get returns the comparable value passed to the Get func // that returned v. func (v *Value) Get() any { return v.cmpVal } // key is a key in our global value map. // It contains type-specialized fields to avoid allocations // when converting common types to empty interfaces. type key struct { s string cmpVal any // isString reports whether key contains a string. // Without it, the zero value of key is ambiguous. isString bool } // keyFor returns a key to use with cmpVal. func keyFor(cmpVal any) key { if s, ok := cmpVal.(string); ok { return key{s: s, isString: true} } return key{cmpVal: cmpVal} } // Value returns a *Value built from k. func (k key) Value() *Value { if k.isString { return &Value{cmpVal: k.s} } return &Value{cmpVal: k.cmpVal} } var ( // mu guards valMap, a weakref map of *Value by underlying value. // It also guards the resurrected field of all *Values. mu sync.Mutex valMap = map[key]uintptr{} // to uintptr(*Value) valSafe = safeMap() // non-nil in safe+leaky mode ) // safeMap returns a non-nil map if we're in safe-but-leaky mode, // as controlled by GODEBUG=intern=leaky func safeMap() map[key]*Value { if godebug.Get("intern") == "leaky" { return map[key]*Value{} } return nil } // Get returns a pointer representing the comparable value cmpVal. // // The returned pointer will be the same for Get(v) and Get(v2) // if and only if v == v2, and can be used as a map key. func Get(cmpVal any) *Value { return get(keyFor(cmpVal)) } // GetByString is identical to Get, except that it is specialized for strings. // This avoids an allocation from putting a string into an interface{} // to pass as an argument to Get. func GetByString(s string) *Value { return get(key{s: s, isString: true}) } // We play unsafe games that violate Go's rules (and assume a non-moving // collector). So we quiet Go here. // See the comment below Get for more implementation details. // //go:nocheckptr func get(k key) *Value { mu.Lock() defer mu.Unlock() var v *Value if valSafe != nil { v = valSafe[k] } else if addr, ok := valMap[k]; ok { v = (*Value)(unsafe.Pointer(addr)) v.resurrected = true } if v != nil { return v } v = k.Value() if valSafe != nil { valSafe[k] = v } else { // SetFinalizer before uintptr conversion (theoretical concern; // see https://github.com/go4org/intern/issues/13) runtime.SetFinalizer(v, finalize) valMap[k] = uintptr(unsafe.Pointer(v)) } return v } func finalize(v *Value) { mu.Lock() defer mu.Unlock() if v.resurrected { // We lost the race. Somebody resurrected it while we // were about to finalize it. Try again next round. v.resurrected = false runtime.SetFinalizer(v, finalize) return } delete(valMap, keyFor(v.cmpVal)) } // Interning is simple if you don't require that unused values be // garbage collectable. But we do require that; we don't want to be // DOS vector. We do this by using a uintptr to hide the pointer from // the garbage collector, and using a finalizer to eliminate the // pointer when no other code is using it. // // The obvious implementation of this is to use a // map[interface{}]uintptr-of-*interface{}, and set up a finalizer to // delete from the map. Unfortunately, this is racy. Because pointers // are being created in violation of Go's unsafety rules, it's // possible to create a pointer to a value concurrently with the GC // concluding that the value can be collected. There are other races // that break the equality invariant as well, but the use-after-free // will cause a runtime crash. // // To make this work, the finalizer needs to know that no references // have been unsafely created since the finalizer was set up. To do // this, values carry a "resurrected" sentinel, which gets set // whenever a pointer is unsafely created. If the finalizer encounters // the sentinel, it clears the sentinel and delays collection for one // additional GC cycle, by re-installing itself as finalizer. This // ensures that the unsafely created pointer is visible to the GC, and // will correctly prevent collection. // // This technique does mean that interned values that get reused take // at least 3 GC cycles to fully collect (1 to clear the sentinel, 1 // to clean up the unsafe map, 1 to be actually deleted). // // @ianlancetaylor commented in // https://github.com/golang/go/issues/41303#issuecomment-717401656 // that it is possible to implement weak references in terms of // finalizers without unsafe. Unfortunately, the approach he outlined // does not work here, for two reasons. First, there is no way to // construct a strong pointer out of a weak pointer; our map stores // weak pointers, but we must return strong pointers to callers. // Second, and more fundamentally, we must return not just _a_ strong // pointer to callers, but _the same_ strong pointer to callers. In // order to return _the same_ strong pointer to callers, we must track // it, which is exactly what we cannot do with strong pointers. // // See https://github.com/inetaf/netaddr/issues/53 for more // discussion, and https://github.com/go4org/intern/issues/2 for an // illustration of the subtleties at play.