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