go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/starlark/int_posix64.go (about) 1 //go:build (linux || darwin || dragonfly || freebsd || netbsd || solaris) && (amd64 || arm64 || mips64x || ppc64 || ppc64le || loong64 || s390x) 2 3 package starlark 4 5 // This file defines an optimized Int implementation for 64-bit machines 6 // running POSIX. It reserves a 4GB portion of the address space using 7 // mmap and represents int32 values as addresses within that range. This 8 // disambiguates int32 values from *big.Int pointers, letting all Int 9 // values be represented as an unsafe.Pointer, so that Int-to-Value 10 // interface conversion need not allocate. 11 12 // Although iOS (which, like macOS, appears as darwin/arm64) is 13 // POSIX-compliant, it limits each process to about 700MB of virtual 14 // address space, which defeats the optimization. Similarly, 15 // OpenBSD's default ulimit for virtual memory is a measly GB or so. 16 // On both those platforms the attempted optimization will fail and 17 // fall back to the slow implementation. 18 19 // An alternative approach to this optimization would be to embed the 20 // int32 values in pointers using odd values, which can be distinguished 21 // from (even) *big.Int pointers. However, the Go runtime does not allow 22 // user programs to manufacture pointers to arbitrary locations such as 23 // within the zero page, or non-span, non-mmap, non-stack locations, 24 // and it may panic if it encounters them; see Issue #382. 25 26 import ( 27 "log" 28 "math" 29 "math/big" 30 "unsafe" 31 32 "golang.org/x/sys/unix" 33 ) 34 35 // intImpl represents a union of (int32, *big.Int) in a single pointer, 36 // so that Int-to-Value conversions need not allocate. 37 // 38 // The pointer is either a *big.Int, if the value is big, or a pointer into a 39 // reserved portion of the address space (smallints), if the value is small 40 // and the address space allocation succeeded. 41 // 42 // See int_generic.go for the basic representation concepts. 43 type intImpl unsafe.Pointer 44 45 // get returns the (small, big) arms of the union. 46 func (i Int) get() (int64, *big.Int) { 47 if smallints == 0 { 48 // optimization disabled 49 if x := (*big.Int)(i.impl); isSmall(x) { 50 return x.Int64(), nil 51 } else { 52 return 0, x 53 } 54 } 55 56 if ptr := uintptr(i.impl); ptr >= smallints && ptr < smallints+1<<32 { 57 return math.MinInt32 + int64(ptr-smallints), nil 58 } 59 return 0, (*big.Int)(i.impl) 60 } 61 62 // Precondition: math.MinInt32 <= x && x <= math.MaxInt32 63 func makeSmallInt(x int64) Int { 64 if smallints == 0 { 65 // optimization disabled 66 return Int{intImpl(big.NewInt(x))} 67 } 68 69 return Int{intImpl(uintptr(x-math.MinInt32) + smallints)} 70 } 71 72 // Precondition: x cannot be represented as int32. 73 func makeBigInt(x *big.Int) Int { return Int{intImpl(x)} } 74 75 // smallints is the base address of a 2^32 byte memory region. 76 // Pointers to addresses in this region represent int32 values. 77 // We assume smallints is not at the very top of the address space. 78 // 79 // Zero means the optimization is disabled and all Ints allocate a big.Int. 80 var smallints = reserveAddresses(1 << 32) 81 82 func reserveAddresses(len int) uintptr { 83 b, err := unix.Mmap(-1, 0, len, unix.PROT_READ, unix.MAP_PRIVATE|unix.MAP_ANON) 84 if err != nil { 85 log.Printf("Starlark failed to allocate 4GB address space: %v. Integer performance may suffer.", err) 86 return 0 // optimization disabled 87 } 88 return uintptr(unsafe.Pointer(&b[0])) 89 }