github.com/shawnclovie/gopher-lua@v0.0.0-20200520092726-90b44ec0e2f2/alloc.go (about)

     1  package lua
     2  
     3  import (
     4  	"reflect"
     5  	"unsafe"
     6  )
     7  
     8  // iface is an internal representation of the go-interface.
     9  type iface struct {
    10  	itab unsafe.Pointer
    11  	word unsafe.Pointer
    12  }
    13  
    14  const preloadLimit LNumber = 128
    15  
    16  var _fv float64
    17  var _uv uintptr
    18  
    19  var preloads [int(preloadLimit)]LValue
    20  
    21  func init() {
    22  	for i := 0; i < int(preloadLimit); i++ {
    23  		preloads[i] = LNumber(i)
    24  	}
    25  }
    26  
    27  // allocator is a fast bulk memory allocator for the LValue.
    28  type allocator struct {
    29  	size    int
    30  	fptrs   []float64
    31  	fheader *reflect.SliceHeader
    32  
    33  	scratchValue  LValue
    34  	scratchValueP *iface
    35  }
    36  
    37  func newAllocator(size int) *allocator {
    38  	al := &allocator{
    39  		size:    size,
    40  		fptrs:   make([]float64, 0, size),
    41  		fheader: nil,
    42  	}
    43  	al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs))
    44  	al.scratchValue = LNumber(0)
    45  	al.scratchValueP = (*iface)(unsafe.Pointer(&al.scratchValue))
    46  
    47  	return al
    48  }
    49  
    50  // LNumber2I takes a number value and returns an interface LValue representing the same number.
    51  // Converting an LNumber to a LValue naively, by doing:
    52  // `var val LValue = myLNumber`
    53  // will result in an individual heap alloc of 8 bytes for the float value. LNumber2I amortizes the cost and memory
    54  // overhead of these allocs by allocating blocks of floats instead.
    55  // The downside of this is that all of the floats on a given block have to become eligible for gc before the block
    56  // as a whole can be gc-ed.
    57  func (al *allocator) LNumber2I(v LNumber) LValue {
    58  	// first check for shared preloaded numbers
    59  	if v >= 0 && v < preloadLimit && float64(v) == float64(int64(v)) {
    60  		return preloads[int(v)]
    61  	}
    62  
    63  	// check if we need a new alloc page
    64  	if cap(al.fptrs) == len(al.fptrs) {
    65  		al.fptrs = make([]float64, 0, al.size)
    66  		al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs))
    67  	}
    68  
    69  	// alloc a new float, and store our value into it
    70  	al.fptrs = append(al.fptrs, float64(v))
    71  	fptr := &al.fptrs[len(al.fptrs)-1]
    72  
    73  	// hack our scratch LValue to point to our allocated value
    74  	// this scratch lvalue is copied when this function returns meaning the scratch value can be reused
    75  	// on the next call
    76  	al.scratchValueP.word = unsafe.Pointer(fptr)
    77  
    78  	return al.scratchValue
    79  }