diff --git a/go.mod b/go.mod index 2e64352f..a9577a64 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,5 @@ require ( github.com/chzyer/logex v1.1.10 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect - golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c // indirect + golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c ) diff --git a/starlark/int.go b/starlark/int.go index ef8f9550..c40532a7 100644 --- a/starlark/int.go +++ b/starlark/int.go @@ -14,30 +14,9 @@ import ( ) // Int is the type of a Starlark int. -type Int struct { - // We use only the signed 32 bit range of small to ensure - // that small+small and small*small do not overflow. - small_ int64 // minint32 <= small <= maxint32 - big_ *big.Int // big != nil <=> value is not representable as int32 -} - -// --- low-level accessors --- - -// get returns the small and big components of the Int. -// small is defined only if big is nil. -func (i Int) get() (small int64, big *big.Int) { - return i.small_, i.big_ -} - -// Precondition: math.MinInt32 <= x && x <= math.MaxInt32 -func makeSmallInt(x int64) Int { - return Int{small_: x} -} - -// Precondition: x cannot be represented as int32. -func makeBigInt(x *big.Int) Int { - return Int{big_: x} -} +// +// The zero value is not a legal value; use MakeInt(0). +type Int struct{ impl intImpl } // --- high-level accessors --- diff --git a/starlark/int_generic.go b/starlark/int_generic.go new file mode 100644 index 00000000..ef7cdae0 --- /dev/null +++ b/starlark/int_generic.go @@ -0,0 +1,33 @@ +//+build !linux,!darwin +//+build !amd64,!arm64,!mips64x,!ppc64x + +package starlark + +// generic Int implementation + +import "math/big" + +type intImpl struct { + // We use only the signed 32-bit range of small to ensure + // that small+small and small*small do not overflow. + small_ int64 // minint32 <= small <= maxint32 + big_ *big.Int // big != nil <=> value is not representable as int32 +} + +// --- low-level accessors --- + +// get returns the small and big components of the Int. +// small is defined only if big is nil. +func (i Int) get() (small int64, big *big.Int) { + return i.small_, i.big_ +} + +// Precondition: math.MinInt32 <= x && x <= math.MaxInt32 +func makeSmallInt(x int64) Int { + return Int{intImpl{small_: x}} +} + +// Precondition: x cannot be represented as int32. +func makeBigInt(x *big.Int) Int { + return Int{intImpl{big_: x}} +} diff --git a/starlark/int_posix64.go b/starlark/int_posix64.go new file mode 100644 index 00000000..51b871e5 --- /dev/null +++ b/starlark/int_posix64.go @@ -0,0 +1,55 @@ +//+build linux darwin +//+build amd64 arm64 mips64x ppc64x + +package starlark + +// Optimized Int implementation for 64-bit machines running POSIX (for mmap). +// It reserves a portion of the address space to represent int32 values. + +import ( + "log" + "math" + "math/big" + "unsafe" + + "golang.org/x/sys/unix" +) + +// intImpl represents a union of (int32, *big.Int) in a single pointer, +// so that Int-to-Value conversions need not allocate. +// +// The pointer is either a *big.Int, if the value is big, or a pointer into a +// reserved portion of the address space (smallints), if the value is small. +// +// See int_generic.go for the basic representation concepts. +type intImpl unsafe.Pointer + +// get returns the (small, big) arms of the union. +func (i Int) get() (int64, *big.Int) { + ptr := uintptr(i.impl) + if ptr >= smallints && ptr < smallints+1<<32 { + return math.MinInt32 + int64(ptr-smallints), nil + } + return 0, (*big.Int)(i.impl) +} + +// Precondition: math.MinInt32 <= x && x <= math.MaxInt32 +func makeSmallInt(x int64) Int { + return Int{intImpl(uintptr(x-math.MinInt32) + smallints)} +} + +// Precondition: x cannot be represented as int32. +func makeBigInt(x *big.Int) Int { return Int{intImpl(x)} } + +// smallints is the base address of a 2^32 byte memory region. +// Pointers to addresses in this region represent int32 values. +// We assume smallints is not at the very top of the address space. +var smallints = reserveAddresses(1 << 32) + +func reserveAddresses(len int) uintptr { + b, err := unix.Mmap(-1, 0, len, unix.PROT_READ, unix.MAP_PRIVATE|unix.MAP_ANONYMOUS) + if err != nil { + log.Fatalf("mmap: %v", err) + } + return uintptr(unsafe.Pointer(&b[0])) +} diff --git a/starlark/testdata/benchmark.star b/starlark/testdata/benchmark.star index 2fc6f027..18a1a2ca 100644 --- a/starlark/testdata/benchmark.star +++ b/starlark/testdata/benchmark.star @@ -32,6 +32,12 @@ def bench_int(): a += 1 def bench_bigint(): - a = 1 << 31 # maxint32 + 1 + a = 1 << 31 # maxint32 + 1 for _ in range1000: a += 1 + +def bench_gauss(): + # Sum of arithmetic series. All results fit in int32. + acc = 0 + for x in range(92000): + acc += x