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  }