diff --git a/alloc.go b/alloc.go deleted file mode 100644 index 96e83c3d..00000000 --- a/alloc.go +++ /dev/null @@ -1,97 +0,0 @@ -package lua - -import ( - "unsafe" -) - -// iface is an internal representation of the go-interface. -type iface struct { - itab unsafe.Pointer - word unsafe.Pointer -} - -const preloadLimit = 256 -const intPreloadLimit = 65536 // Common loop bounds - -var preloads [preloadLimit]LValue -var intPreloads [intPreloadLimit * 2]LValue // [-65536, 65536) - -func init() { - for i := 0; i < preloadLimit; i++ { - preloads[i] = LNumber(i) - } - for i := -intPreloadLimit; i < intPreloadLimit; i++ { - intPreloads[i+intPreloadLimit] = LInteger(i) - } -} - -// allocator is a fast bulk memory allocator for the LValue. -// Uses a single backing slice for both float64 and int64 since both are 8 bytes. -type allocator struct { - size int - ptrs []uint64 // shared backing store for float64 and int64 - - scratchValue LValue - scratchValueP *iface - scratchIntVal LValue - scratchIntValP *iface -} - -func newAllocator(size int) *allocator { - al := &allocator{ - size: size, - ptrs: nil, // lazy alloc on first use - } - al.scratchValue = LNumber(0) - al.scratchValueP = (*iface)(unsafe.Pointer(&al.scratchValue)) - al.scratchIntVal = LInteger(0) - al.scratchIntValP = (*iface)(unsafe.Pointer(&al.scratchIntVal)) - - return al -} - -// LNumber2I takes a number value and returns an interface LValue representing the same number. -// Converting an LNumber to a LValue naively, by doing: -// `var val LValue = myLNumber` -// will result in an individual heap alloc of 8 bytes for the float value. LNumber2I amortizes the cost and memory -// overhead of these allocs by allocating blocks instead. -// The downside of this is that all values on a given block have to become eligible for gc before the block -// as a whole can be gc-ed. -func (al *allocator) LNumber2I(v LNumber) LValue { - // Fast path: check for shared preloaded integers [0, preloadLimit) - iv := int(v) - if iv >= 0 && iv < preloadLimit && LNumber(iv) == v { - return preloads[iv] - } - - // check if we need a new alloc page - if cap(al.ptrs) == len(al.ptrs) { - al.ptrs = make([]uint64, 0, al.size) - } - - // alloc from shared pool, reinterpret as float64 - al.ptrs = append(al.ptrs, *(*uint64)(unsafe.Pointer(&v))) - ptr := &al.ptrs[len(al.ptrs)-1] - - al.scratchValueP.word = unsafe.Pointer(ptr) - return al.scratchValue -} - -// LInteger2I converts an LInteger to LValue with zero-alloc for values in [-65536, 65536). -func (al *allocator) LInteger2I(v LInteger) LValue { - iv := int(v) - if iv >= -intPreloadLimit && iv < intPreloadLimit { - return intPreloads[iv+intPreloadLimit] - } - - if cap(al.ptrs) == len(al.ptrs) { - al.ptrs = make([]uint64, 0, al.size) - } - - // alloc from shared pool, reinterpret as int64 - al.ptrs = append(al.ptrs, uint64(v)) - ptr := &al.ptrs[len(al.ptrs)-1] - - al.scratchIntValP.word = unsafe.Pointer(ptr) - return al.scratchIntVal -} diff --git a/alloc_test.go b/alloc_test.go deleted file mode 100644 index d02891ac..00000000 --- a/alloc_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package lua - -import ( - "math" - "testing" -) - -func TestAllocatorLNumber2I_Preloaded(t *testing.T) { - al := newAllocator(32) - - // Test preloaded range [0, 256) - for i := 0; i < preloadLimit; i++ { - v := al.LNumber2I(LNumber(i)) - if v.Type() != LTNumber { - t.Errorf("preloaded %d: expected LTNumber, got %v", i, v.Type()) - } - if float64(v.(LNumber)) != float64(i) { - t.Errorf("preloaded %d: expected %d, got %v", i, i, v) - } - // Verify it's the same preloaded instance - if v != preloads[i] { - t.Errorf("preloaded %d: not using preloaded instance", i) - } - } -} - -func TestAllocatorLNumber2I_OutsidePreload(t *testing.T) { - al := newAllocator(32) - - tests := []LNumber{ - LNumber(preloadLimit), // just outside preload - LNumber(preloadLimit + 1), // outside preload - LNumber(1000), // larger value - LNumber(-1), // negative - LNumber(-1000), // larger negative - LNumber(0.5), // float - LNumber(1.5), // float that's not integer - LNumber(math.Pi), // irrational - LNumber(math.MaxFloat64), // max float - LNumber(-math.MaxFloat64), // min float - } - - for _, num := range tests { - v := al.LNumber2I(num) - if v.Type() != LTNumber { - t.Errorf("LNumber(%v): expected LTNumber, got %v", num, v.Type()) - } - if float64(v.(LNumber)) != float64(num) { - t.Errorf("LNumber(%v): expected %v, got %v", num, num, v) - } - } -} - -func TestAllocatorLNumber2I_FloatIntBoundary(t *testing.T) { - al := newAllocator(32) - - // Test that 5.0 uses preload but 5.5 doesn't - v5 := al.LNumber2I(LNumber(5)) - if v5 != preloads[5] { - t.Error("5.0 should use preloaded value") - } - - v55 := al.LNumber2I(LNumber(5.5)) - if v55 == preloads[5] { - t.Error("5.5 should not use preloaded value") - } -} - -func TestAllocatorLInteger2I_Preloaded(t *testing.T) { - al := newAllocator(32) - - // Test preloaded range [-65536, 65536) - testCases := []int64{ - 0, 1, -1, 100, -100, - intPreloadLimit - 1, - -intPreloadLimit, - } - - for _, i := range testCases { - v := al.LInteger2I(LInteger(i)) - if v.Type() != LTInteger { - t.Errorf("preloaded %d: expected LTInteger, got %v", i, v.Type()) - } - if int64(v.(LInteger)) != i { - t.Errorf("preloaded %d: expected %d, got %v", i, i, v) - } - // Verify it's the same preloaded instance - if v != intPreloads[i+intPreloadLimit] { - t.Errorf("preloaded %d: not using preloaded instance", i) - } - } -} - -func TestAllocatorLInteger2I_OutsidePreload(t *testing.T) { - al := newAllocator(32) - - tests := []LInteger{ - LInteger(intPreloadLimit), // just outside preload - LInteger(-intPreloadLimit - 1), // just outside negative preload - LInteger(100000), // larger value - LInteger(-100000), // larger negative - LInteger(math.MaxInt64), // max int - LInteger(math.MinInt64), // min int - } - - for _, num := range tests { - v := al.LInteger2I(num) - if v.Type() != LTInteger { - t.Errorf("LInteger(%v): expected LTInteger, got %v", num, v.Type()) - } - if int64(v.(LInteger)) != int64(num) { - t.Errorf("LInteger(%v): expected %v, got %v", num, num, v) - } - } -} - -func TestAllocatorPageAllocation(t *testing.T) { - al := newAllocator(4) // small page size - - // Force multiple page allocations - values := make([]LValue, 20) - for i := 0; i < 20; i++ { - values[i] = al.LNumber2I(LNumber(1000 + i)) // outside preload range - } - - // Verify all values are correct - for i := 0; i < 20; i++ { - expected := LNumber(1000 + i) - if float64(values[i].(LNumber)) != float64(expected) { - t.Errorf("value %d: expected %v, got %v", i, expected, values[i]) - } - } -} - -func TestAllocatorPreloadInit(t *testing.T) { - // Verify preloads are initialized correctly - for i := 0; i < preloadLimit; i++ { - if preloads[i].Type() != LTNumber { - t.Errorf("preloads[%d] type: expected LTNumber, got %v", i, preloads[i].Type()) - } - if float64(preloads[i].(LNumber)) != float64(i) { - t.Errorf("preloads[%d]: expected %d, got %v", i, i, preloads[i]) - } - } - - // Verify intPreloads are initialized correctly - for i := -intPreloadLimit; i < intPreloadLimit; i++ { - idx := i + intPreloadLimit - if intPreloads[idx].Type() != LTInteger { - t.Errorf("intPreloads[%d] type: expected LTInteger, got %v", i, intPreloads[idx].Type()) - } - if int64(intPreloads[idx].(LInteger)) != int64(i) { - t.Errorf("intPreloads[%d]: expected %d, got %v", i, i, intPreloads[idx]) - } - } -} - -func TestAllocatorSpecialFloats(t *testing.T) { - al := newAllocator(32) - - // Test special float values - tests := []struct { - name string - val LNumber - }{ - {"positive infinity", LNumber(math.Inf(1))}, - {"negative infinity", LNumber(math.Inf(-1))}, - {"NaN", LNumber(math.NaN())}, - {"smallest positive", LNumber(math.SmallestNonzeroFloat64)}, - {"negative zero", LNumber(math.Copysign(0, -1))}, - } - - for _, tt := range tests { - v := al.LNumber2I(tt.val) - got := float64(v.(LNumber)) - - if math.IsNaN(float64(tt.val)) { - if !math.IsNaN(got) { - t.Errorf("%s: expected NaN, got %v", tt.name, got) - } - } else if got != float64(tt.val) { - t.Errorf("%s: expected %v, got %v", tt.name, tt.val, got) - } - } -} - -func BenchmarkAllocatorLNumber2I_Preloaded(b *testing.B) { - al := newAllocator(32) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = al.LNumber2I(LNumber(i % preloadLimit)) - } -} - -func BenchmarkAllocatorLNumber2I_NonPreloaded(b *testing.B) { - al := newAllocator(1024) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = al.LNumber2I(LNumber(i + preloadLimit)) - } -} - -func BenchmarkAllocatorLInteger2I_Preloaded(b *testing.B) { - al := newAllocator(32) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = al.LInteger2I(LInteger(i % intPreloadLimit)) - } -} - -func BenchmarkAllocatorLInteger2I_NonPreloaded(b *testing.B) { - al := newAllocator(1024) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = al.LInteger2I(LInteger(i + intPreloadLimit)) - } -} diff --git a/integer_test.go b/integer_test.go index 8484a79c..4388fdc5 100644 --- a/integer_test.go +++ b/integer_test.go @@ -30,11 +30,9 @@ func TestLInteger_TypeName(t *testing.T) { } func TestLInteger_Preloads(t *testing.T) { - al := newAllocator(64) - // Values in [-256, 256) should be preloaded (zero alloc) for i := int64(-256); i < 256; i++ { - v := al.LInteger2I(LInteger(i)) + v := lintegerToValue(LInteger(i)) if v.Type() != LTInteger { t.Errorf("LInteger(%d) should have type LTInteger", i) } @@ -45,12 +43,10 @@ func TestLInteger_Preloads(t *testing.T) { } func TestLInteger_LargeValues(t *testing.T) { - al := newAllocator(64) - // Values outside preload range should also work large := []int64{1000, -1000, 1 << 62, -(1 << 62)} for _, i := range large { - v := al.LInteger2I(LInteger(i)) + v := lintegerToValue(LInteger(i)) if v.Type() != LTInteger { t.Errorf("LInteger(%d) should have type LTInteger", i) } diff --git a/registry.go b/registry.go index 422aa2ae..dc9f9b31 100644 --- a/registry.go +++ b/registry.go @@ -10,12 +10,11 @@ type registry struct { top int growBy int maxSize int - alloc *allocator handler registryHandler } -func newRegistry(handler registryHandler, initialSize int, growBy int, maxSize int, alloc *allocator) *registry { - return ®istry{make([]LValue, initialSize), 0, growBy, maxSize, alloc, handler} +func newRegistry(handler registryHandler, initialSize int, growBy int, maxSize int) *registry { + return ®istry{make([]LValue, initialSize), 0, growBy, maxSize, handler} } func (rg *registry) resize(requiredSize int) bool { // +inline-start @@ -266,7 +265,7 @@ func (rg *registry) SetNumber(regi int, vali LNumber) { // +inline-start rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } diff --git a/registry_test.go b/registry_test.go index b5acbd78..0139d148 100644 --- a/registry_test.go +++ b/registry_test.go @@ -14,8 +14,7 @@ func (h *testRegistryHandler) registryOverflow() { func TestRegistryBasicOperations(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 10, 5, 100, alloc) + rg := newRegistry(handler, 10, 5, 100) // Test initial state if rg.Top() != 0 { @@ -51,8 +50,7 @@ func TestRegistryBasicOperations(t *testing.T) { func TestRegistrySetTop(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 10, 5, 100, alloc) + rg := newRegistry(handler, 10, 5, 100) // Push some values rg.Push(LNumber(1)) @@ -80,8 +78,7 @@ func TestRegistrySetTop(t *testing.T) { func TestRegistrySet(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 10, 5, 100, alloc) + rg := newRegistry(handler, 10, 5, 100) // Set beyond current top rg.Set(5, LNumber(100)) @@ -101,8 +98,7 @@ func TestRegistrySet(t *testing.T) { func TestRegistryResize(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 2, 2, 100, alloc) // small initial size + rg := newRegistry(handler, 2, 2, 100) // small initial size // Push beyond initial capacity for i := 0; i < 10; i++ { @@ -123,8 +119,7 @@ func TestRegistryResize(t *testing.T) { func TestRegistryOverflow(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 2, 2, 5, alloc) // small max size + rg := newRegistry(handler, 2, 2, 5) // small max size // Push until we hit max size for i := 0; i < 5; i++ { @@ -147,8 +142,7 @@ func TestRegistryOverflow(t *testing.T) { func TestRegistryCopyRange(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 20, 5, 100, alloc) + rg := newRegistry(handler, 20, 5, 100) // Setup: push 5 values for i := 0; i < 5; i++ { @@ -176,8 +170,7 @@ func TestRegistryCopyRange(t *testing.T) { func TestRegistryFillNil(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 20, 5, 100, alloc) + rg := newRegistry(handler, 20, 5, 100) // Setup: push some values rg.Push(LNumber(1)) @@ -200,8 +193,7 @@ func TestRegistryFillNil(t *testing.T) { func TestRegistryInsert(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 20, 5, 100, alloc) + rg := newRegistry(handler, 20, 5, 100) // Push initial values rg.Push(LNumber(1)) @@ -225,8 +217,7 @@ func TestRegistryInsert(t *testing.T) { func TestRegistryIsFull(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 3, 2, 100, alloc) + rg := newRegistry(handler, 3, 2, 100) if rg.IsFull() { t.Error("registry should not be full initially") @@ -249,10 +240,9 @@ func TestRegistryIsFull(t *testing.T) { func TestRegistrySetNumber(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 10, 5, 100, alloc) + rg := newRegistry(handler, 10, 5, 100) - // SetNumber uses allocator for non-preloaded values + // SetNumber uses number boxing helper for non-preloaded values. rg.SetNumber(0, LNumber(1000)) // outside preload range v := rg.Get(0) @@ -266,8 +256,7 @@ func TestRegistrySetNumber(t *testing.T) { func TestRegistryGetNilValue(t *testing.T) { handler := &testRegistryHandler{} - alloc := newAllocator(32) - rg := newRegistry(handler, 10, 5, 100, alloc) + rg := newRegistry(handler, 10, 5, 100) rg.SetTop(5) @@ -281,8 +270,7 @@ func TestRegistryGetNilValue(t *testing.T) { func BenchmarkRegistryPush(b *testing.B) { handler := &testRegistryHandler{} - alloc := newAllocator(1024) - rg := newRegistry(handler, 1024, 256, 10000, alloc) + rg := newRegistry(handler, 1024, 256, 10000) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -295,8 +283,7 @@ func BenchmarkRegistryPush(b *testing.B) { func BenchmarkRegistrySetNumber(b *testing.B) { handler := &testRegistryHandler{} - alloc := newAllocator(1024) - rg := newRegistry(handler, 1024, 256, 10000, alloc) + rg := newRegistry(handler, 1024, 256, 10000) b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/state.go b/state.go index ffb068c1..51ecbaa4 100644 --- a/state.go +++ b/state.go @@ -445,8 +445,8 @@ func panicWithoutTraceback(L *LState) { func newLState(options Options) *LState { // Try to get a state from the pool if pooled := statePool.Get(); pooled != nil { - if ls, ok := pooled.(*LState); ok && ls != nil && ls.alloc != nil { - // Reuse pooled state with its allocator + if ls, ok := pooled.(*LState); ok && ls != nil { + // Reuse pooled state. ls.G = newGlobal() ls.Parent = nil ls.Panic = panicWithTraceback @@ -461,11 +461,6 @@ func newLState(options Options) *LState { ls.ctx = nil ls.ctxDone = nil - // Reset allocator slice but keep capacity - if ls.alloc.ptrs != nil { - ls.alloc.ptrs = ls.alloc.ptrs[:0] - } - // Reuse or recreate registry if ls.reg != nil && cap(ls.reg.array) >= options.RegistrySize { ls.reg.handler = ls @@ -473,7 +468,7 @@ func newLState(options Options) *LState { ls.reg.maxSize = options.RegistryMaxSize ls.reg.growBy = options.RegistryGrowStep } else { - ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, ls.alloc) + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize) } // Reuse auto-growing stack (can handle any size), recreate fixed stacks @@ -494,7 +489,6 @@ func newLState(options Options) *LState { } // Create fresh state - al := newAllocator(64) ls := &LState{ G: newGlobal(), Parent: nil, @@ -503,7 +497,6 @@ func newLState(options Options) *LState { Options: options, stop: 0, - alloc: al, currentFrame: nil, wrapped: false, uvcache: nil, @@ -516,7 +509,7 @@ func newLState(options Options) *LState { } else { ls.stack = newFixedCallFrameStack(options.CallStackSize) } - ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, al) + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize) ls.Env = ls.G.Global return ls } @@ -1423,7 +1416,7 @@ func (ls *LState) CreateTable(acap, hcap int) *LTable { // NewThreadWithContext returns a new LState with the given context. // Pass nil for no context (faster execution without cancellation checks). func (ls *LState) NewThreadWithContext(ctx context.Context) *LState { - thread := newLStateWithGAndAlloc(ls.Options, ls.G, ls.Env, ls.alloc) + thread := newLStateWithGlobal(ls.Options, ls.G, ls.Env) if ctx != nil { thread.mainLoop = mainLoopWithContext thread.ctx = ctx diff --git a/state_pool.go b/state_pool.go index 5b03ead2..daab5692 100644 --- a/state_pool.go +++ b/state_pool.go @@ -65,8 +65,8 @@ func (ls *LState) Close() { } } -// newLStateWithGAndAlloc creates a thread that shares the parent's allocator -func newLStateWithGAndAlloc(options Options, G *Global, env *LTable, parentAlloc *allocator) *LState { +// newLStateWithGlobal creates a thread that shares the parent's global/env. +func newLStateWithGlobal(options Options, G *Global, env *LTable) *LState { // Try to get a state from the pool pooledState := statePool.Get() @@ -77,17 +77,15 @@ func newLStateWithGAndAlloc(options Options, G *Global, env *LTable, parentAlloc ls.Panic = panicWithTraceback ls.Options = options ls.mainLoop = mainLoop - ls.alloc = parentAlloc ls.stop = 0 ls.ctx = nil ls.ctxDone = nil // Registry was preserved but might need resetting if options changed if ls.reg != nil && cap(ls.reg.array) != options.RegistrySize { - ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, parentAlloc) + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize) } else if ls.reg != nil { ls.reg.handler = ls - ls.reg.alloc = parentAlloc } return ls @@ -101,7 +99,6 @@ func newLStateWithGAndAlloc(options Options, G *Global, env *LTable, parentAlloc Dead: false, Options: options, stop: 0, - alloc: parentAlloc, currentFrame: nil, wrapped: false, uvcache: nil, @@ -116,7 +113,7 @@ func newLStateWithGAndAlloc(options Options, G *Global, env *LTable, parentAlloc ls.stack = newFixedCallFrameStack(options.CallStackSize) } - ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, parentAlloc) + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize) ls.Env = env return ls diff --git a/state_test.go b/state_test.go index c70459f5..3e8dc1f0 100644 --- a/state_test.go +++ b/state_test.go @@ -746,9 +746,8 @@ func (registryTestHandler) registryOverflow() { // test pushing and popping from the registry func BenchmarkRegistryPushPopAutoGrow(t *testing.B) { - al := newAllocator(32) sz := 256 * 20 - reg := newRegistry(registryTestHandler(0), sz/2, 64, sz, al) + reg := newRegistry(registryTestHandler(0), sz/2, 64, sz) value := LString("test") t.ResetTimer() @@ -764,9 +763,8 @@ func BenchmarkRegistryPushPopAutoGrow(t *testing.B) { } func BenchmarkRegistryPushPopFixed(t *testing.B) { - al := newAllocator(32) sz := 256 * 20 - reg := newRegistry(registryTestHandler(0), sz, 0, sz, al) + reg := newRegistry(registryTestHandler(0), sz, 0, sz) value := LString("test") t.ResetTimer() @@ -782,9 +780,8 @@ func BenchmarkRegistryPushPopFixed(t *testing.B) { } func BenchmarkRegistrySetTop(t *testing.B) { - al := newAllocator(32) sz := 256 * 20 - reg := newRegistry(registryTestHandler(0), sz, 32, sz*2, al) + reg := newRegistry(registryTestHandler(0), sz, 32, sz*2) t.ResetTimer() diff --git a/utils_test.go b/utils_test.go index 2eda3743..58b11f5b 100644 --- a/utils_test.go +++ b/utils_test.go @@ -61,29 +61,25 @@ func TestIsArrayKey(t *testing.T) { } func TestLNumber2IPreload(t *testing.T) { - al := newAllocator(32) - for i := 0; i < preloadLimit; i++ { - v := al.LNumber2I(LNumber(i)) - if v != preloads[i] { - t.Errorf("LNumber2I(%d) did not return preloaded value", i) + v := lnumberToValue(LNumber(i)) + if v != preloadedNumbers[i] { + t.Errorf("lnumberToValue(%d) did not return preloaded value", i) } } - v := al.LNumber2I(LNumber(preloadLimit)) - if v == preloads[int(preloadLimit)-1] { - t.Errorf("LNumber2I(%d) should not return preloaded value", preloadLimit) + v := lnumberToValue(LNumber(preloadLimit)) + if v == preloadedNumbers[int(preloadLimit)-1] { + t.Errorf("lnumberToValue(%d) should not return preloaded value", preloadLimit) } } func TestLNumber2INonInteger(t *testing.T) { - al := newAllocator(32) - tests := []LNumber{0.5, 1.5, -0.5, math.Pi, 127.5} for _, v := range tests { - result := al.LNumber2I(v) + result := lnumberToValue(v) if n, ok := result.(LNumber); !ok || n != v { - t.Errorf("LNumber2I(%v) returned incorrect value", v) + t.Errorf("lnumberToValue(%v) returned incorrect value", v) } } } diff --git a/value.go b/value.go index e5a43464..15ce7aa3 100644 --- a/value.go +++ b/value.go @@ -225,7 +225,6 @@ type LState struct { stop int32 reg *registry stack callFrameStack - alloc *allocator currentFrame *callFrame wrapped bool uvcache *Upvalue diff --git a/value_boxing.go b/value_boxing.go new file mode 100644 index 00000000..03a905f2 --- /dev/null +++ b/value_boxing.go @@ -0,0 +1,32 @@ +package lua + +const preloadLimit = 256 +const intPreloadLimit = 65536 // Common loop bounds + +var preloadedNumbers [preloadLimit]LValue +var preloadedIntegers [intPreloadLimit * 2]LValue // [-65536, 65536) + +func init() { + for i := 0; i < preloadLimit; i++ { + preloadedNumbers[i] = LNumber(i) + } + for i := -intPreloadLimit; i < intPreloadLimit; i++ { + preloadedIntegers[i+intPreloadLimit] = LInteger(i) + } +} + +func lnumberToValue(v LNumber) LValue { + iv := int(v) + if iv >= 0 && iv < preloadLimit && LNumber(iv) == v { + return preloadedNumbers[iv] + } + return v +} + +func lintegerToValue(v LInteger) LValue { + iv := int(v) + if iv >= -intPreloadLimit && iv < intPreloadLimit { + return preloadedIntegers[iv+intPreloadLimit] + } + return v +} diff --git a/vm.go b/vm.go index 77d46394..ef64236a 100644 --- a/vm.go +++ b/vm.go @@ -552,7 +552,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { // Fast path: both integers if lhsI, ok1 := lhs.(LInteger); ok1 { if rhsI, ok2 := rhs.(LInteger); ok2 { - v := reg.alloc.LInteger2I(lhsI + rhsI) + v := lintegerToValue(lhsI + rhsI) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -567,7 +567,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { // Fast path: both numbers if lhsN, ok1 := lhs.(LNumber); ok1 { if rhsN, ok2 := rhs.(LNumber); ok2 { - v := reg.alloc.LNumber2I(lhsN + rhsN) + v := lnumberToValue(lhsN + rhsN) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -583,7 +583,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { v1, ok1 := toNumber(lhs) v2, ok2 := toNumber(rhs) if ok1 && ok2 { - v := reg.alloc.LNumber2I(v1 + v2) + v := lnumberToValue(v1 + v2) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -628,7 +628,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { // Fast path: both integers if lhsI, ok1 := lhs.(LInteger); ok1 { if rhsI, ok2 := rhs.(LInteger); ok2 { - v := reg.alloc.LInteger2I(lhsI - rhsI) + v := lintegerToValue(lhsI - rhsI) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -643,7 +643,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { // Fast path: both numbers if lhsN, ok1 := lhs.(LNumber); ok1 { if rhsN, ok2 := rhs.(LNumber); ok2 { - v := reg.alloc.LNumber2I(lhsN - rhsN) + v := lnumberToValue(lhsN - rhsN) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -659,7 +659,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { v1, ok1 := toNumber(lhs) v2, ok2 := toNumber(rhs) if ok1 && ok2 { - v := reg.alloc.LNumber2I(v1 - v2) + v := lnumberToValue(v1 - v2) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -704,7 +704,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { // Fast path: both integers if lhsI, ok1 := lhs.(LInteger); ok1 { if rhsI, ok2 := rhs.(LInteger); ok2 { - v := reg.alloc.LInteger2I(lhsI * rhsI) + v := lintegerToValue(lhsI * rhsI) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -719,7 +719,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { // Fast path: both numbers if lhsN, ok1 := lhs.(LNumber); ok1 { if rhsN, ok2 := rhs.(LNumber); ok2 { - v := reg.alloc.LNumber2I(lhsN * rhsN) + v := lnumberToValue(lhsN * rhsN) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -735,7 +735,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { v1, ok1 := toNumber(lhs) v2, ok2 := toNumber(rhs) if ok1 && ok2 { - v := reg.alloc.LNumber2I(v1 * v2) + v := lnumberToValue(v1 * v2) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -784,7 +784,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -841,7 +841,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -898,7 +898,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -967,7 +967,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { if (l^r) < 0 && l%r != 0 { q-- } - vali := rg.alloc.LInteger2I(LInteger(q)) + vali := lintegerToValue(LInteger(q)) newSize := regi + 1 if newSize > cap(rg.array) { rg.resize(newSize) @@ -1009,7 +1009,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { { rg := reg regi := RA - vali := rg.alloc.LInteger2I(LInteger(l & r)) + vali := lintegerToValue(LInteger(l & r)) newSize := regi + 1 if newSize > cap(rg.array) { rg.resize(newSize) @@ -1051,7 +1051,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { { rg := reg regi := RA - vali := rg.alloc.LInteger2I(LInteger(l | r)) + vali := lintegerToValue(LInteger(l | r)) newSize := regi + 1 if newSize > cap(rg.array) { rg.resize(newSize) @@ -1093,7 +1093,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { { rg := reg regi := RA - vali := rg.alloc.LInteger2I(LInteger(l ^ r)) + vali := lintegerToValue(LInteger(l ^ r)) newSize := regi + 1 if newSize > cap(rg.array) { rg.resize(newSize) @@ -1143,7 +1143,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { { rg := reg regi := RA - vali := rg.alloc.LInteger2I(LInteger(result)) + vali := lintegerToValue(LInteger(result)) newSize := regi + 1 if newSize > cap(rg.array) { rg.resize(newSize) @@ -1193,7 +1193,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { { rg := reg regi := RA - vali := rg.alloc.LInteger2I(LInteger(result)) + vali := lintegerToValue(LInteger(result)) newSize := regi + 1 if newSize > cap(rg.array) { rg.resize(newSize) @@ -1217,7 +1217,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { { rg := reg regi := RA - vali := rg.alloc.LNumber2I(-nm) + vali := lnumberToValue(-nm) newSize := regi + 1 // this section is inlined by go-inline // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' @@ -1308,7 +1308,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { { rg := reg regi := RA - vali := rg.alloc.LInteger2I(LInteger(^n)) + vali := lintegerToValue(LInteger(^n)) newSize := regi + 1 if newSize > cap(rg.array) { rg.resize(newSize) @@ -1392,7 +1392,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -1421,7 +1421,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -1464,7 +1464,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -2237,7 +2237,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { init := int64(initI) + int64(stepI) limit := int64(limitI) step := int64(stepI) - v := reg.alloc.LInteger2I(LInteger(init)) + v := lintegerToValue(LInteger(init)) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -2295,7 +2295,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -2314,7 +2314,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { rg.resize(requiredSize) } } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 } @@ -2373,7 +2373,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { if initI, ok1 := initVal.(LInteger); ok1 { if stepI, ok2 := stepVal.(LInteger); ok2 { result := int64(initI) - int64(stepI) - v := reg.alloc.LInteger2I(LInteger(result)) + v := lintegerToValue(LInteger(result)) newSize := RA + 1 if newSize > cap(reg.array) { reg.resize(newSize) @@ -2397,7 +2397,7 @@ func mainLoopWithContext(L *LState, baseframe *callFrame) { if newSize > cap(rg.array) { rg.resize(newSize) } - rg.array[regi] = rg.alloc.LNumber2I(vali) + rg.array[regi] = lnumberToValue(vali) if regi >= rg.top { rg.top = regi + 1 }