From b767b171e7aa60700cdcb2c2b9a124269cd6a73c Mon Sep 17 00:00:00 2001 From: Anthony Regeda Date: Fri, 7 May 2021 01:41:15 +0200 Subject: [PATCH] Remove memory hard limits Heap, stack and grid can grow infinitely. Memory might be preallocated with certain elements in the buffer. --- README.md | 2 +- assert/assert.go | 18 +++--- bench_test.go | 11 +++- delegate/delegator.go | 14 ++-- exec/stack.go | 35 +++------- exec/vm.go | 66 ++++++++++++------- exec/vm_test.go | 2 +- memory/addr.go | 39 +++++------ memory/addr_test.go | 14 ++-- memory/errors.go | 10 --- memory/grid.go | 36 +++++++---- memory/heap.go | 30 +++++---- memory/limits.go | 15 ----- memory/links.go | 23 ------- memory/memory.go | 104 ++++++++++++++++-------------- memory/memory_test.go | 147 ++++++------------------------------------ memory/type.go | 4 +- memory/type_test.go | 1 + stdlib/compare.go | 18 +++--- stdlib/strings.go | 16 ++--- 20 files changed, 239 insertions(+), 366 deletions(-) delete mode 100644 memory/errors.go delete mode 100644 memory/limits.go delete mode 100644 memory/links.go diff --git a/README.md b/README.md index b0e7117..840177e 100644 --- a/README.md +++ b/README.md @@ -163,5 +163,5 @@ equals("foo,bar,baz", join(",", ["foo", "bar", "baz"])) ``` cpu: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz BenchmarkExec -BenchmarkExec-8 1746600 673.2 ns/op 0 B/op 0 allocs/op +BenchmarkExec-8 1318488 887.4 ns/op 0 B/op 0 allocs/op ``` diff --git a/assert/assert.go b/assert/assert.go index 54c9ca8..16d2d82 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -11,17 +11,17 @@ var ( ) type Asserter interface { - Assert([]*memory.Addr) error + Assert([]memory.Addr) error } -type AsserterFunc func([]*memory.Addr) error +type AsserterFunc func([]memory.Addr) error -func (a AsserterFunc) Assert(argv []*memory.Addr) error { +func (a AsserterFunc) Assert(argv []memory.Addr) error { return a(argv) } func Len(l int) AsserterFunc { - return func(argv []*memory.Addr) error { + return func(argv []memory.Addr) error { if len(argv) != l { return errWrongArgsNumber } @@ -30,7 +30,7 @@ func Len(l int) AsserterFunc { } func TypeAt(i int, t memory.Type) AsserterFunc { - return func(argv []*memory.Addr) error { + return func(argv []memory.Addr) error { if argv[i].Type() != t { return errWrongArgType } @@ -39,13 +39,13 @@ func TypeAt(i int, t memory.Type) AsserterFunc { } func VectorAt(i int, a Asserter) AsserterFunc { - return func(argv []*memory.Addr) error { + return func(argv []memory.Addr) error { return a.Assert(argv[i].Vector()) } } func Type(t memory.Type) AsserterFunc { - return func(argv []*memory.Addr) error { + return func(argv []memory.Addr) error { for _, arg := range argv { if arg.Type() != t { return errWrongArgType @@ -56,7 +56,7 @@ func Type(t memory.Type) AsserterFunc { } func Any(a ...Asserter) AsserterFunc { - return func(argv []*memory.Addr) error { + return func(argv []memory.Addr) error { var lastErr error for _, aa := range a { err := aa.Assert(argv) @@ -70,7 +70,7 @@ func Any(a ...Asserter) AsserterFunc { } func Every(a ...Asserter) AsserterFunc { - return func(argv []*memory.Addr) error { + return func(argv []memory.Addr) error { for _, aa := range a { if err := aa.Assert(argv); err != nil { return err diff --git a/bench_test.go b/bench_test.go index c4da8c9..b896344 100644 --- a/bench_test.go +++ b/bench_test.go @@ -6,12 +6,21 @@ import ( "github.com/regeda/expr/exec" "github.com/regeda/expr/internal/ast/value" "github.com/regeda/expr/internal/compiler" + "github.com/regeda/expr/memory" "github.com/regeda/expr/stdlib" ) func BenchmarkExec(b *testing.B) { comp := compiler.New() - vm := exec.New(stdlib.Registry()) + vm := exec.New(stdlib.Registry(), + exec.WithStackSize(0xff), + exec.WithMemory( + memory.New( + memory.PreallocHeap(0xff), + memory.PreallocGrid(0xff), + ), + ), + ) bcode := comp.Compile(value.Nest( value.Exit(), diff --git a/delegate/delegator.go b/delegate/delegator.go index 2efe529..1e44e0a 100644 --- a/delegate/delegator.go +++ b/delegate/delegator.go @@ -6,20 +6,20 @@ import ( ) type Delegator interface { - Delegate(*memory.Memory, []*memory.Addr) (*memory.Addr, error) + Delegate(*memory.Memory, []memory.Addr) (memory.Addr, error) } -type DelegatorFunc func(*memory.Memory, []*memory.Addr) (*memory.Addr, error) +type DelegatorFunc func(*memory.Memory, []memory.Addr) (memory.Addr, error) -func (f DelegatorFunc) Delegate(memory *memory.Memory, argv []*memory.Addr) (*memory.Addr, error) { - return f(memory, argv) +func (f DelegatorFunc) Delegate(mem *memory.Memory, argv []memory.Addr) (memory.Addr, error) { + return f(mem, argv) } func (f DelegatorFunc) Assert(a assert.Asserter) DelegatorFunc { - return func(memory *memory.Memory, argv []*memory.Addr) (*memory.Addr, error) { + return func(mem *memory.Memory, argv []memory.Addr) (memory.Addr, error) { if err := a.Assert(argv); err != nil { - return nil, err + return memory.Nil, err } - return f(memory, argv) + return f(mem, argv) } } diff --git a/exec/stack.go b/exec/stack.go index de905fd..6d2e371 100644 --- a/exec/stack.go +++ b/exec/stack.go @@ -6,38 +6,23 @@ import ( "github.com/regeda/expr/memory" ) -const ( - stacklimit = 0xff -) - -type stack struct { - p uint32 - seq [stacklimit]*memory.Addr -} +type stack []memory.Addr func (s *stack) reset() { - s.p = 0 -} - -func (s *stack) push(a *memory.Addr) error { - if s.p == uint32(len(s.seq)) { - return errStackOverflow - } - s.seq[s.p] = a - s.p++ - return nil + *s = (*s)[:0] } -func (s *stack) tail(n uint32) bool { - return s.p-n >= 0 +func (s *stack) push(a memory.Addr) { + *s = append(*s, a) } -func (s *stack) pop(n uint32) ([]*memory.Addr, error) { - if !s.tail(n) { +func (s *stack) pop(n uint32) ([]memory.Addr, error) { + l := uint32(len(*s)) + if n > l { return nil, fmt.Errorf("stack tail shorter than %d", n) } - offset := s.p - n - a := s.seq[offset:s.p] - s.p = offset + offset := l - n + a := (*s)[offset:l] + *s = (*s)[:offset] return a, nil } diff --git a/exec/vm.go b/exec/vm.go index df296fb..d97d82b 100644 --- a/exec/vm.go +++ b/exec/vm.go @@ -20,10 +20,30 @@ type VM struct { prog bytecode.Program } -func New(delegators map[string]delegate.Delegator) *VM { - return &VM{ +type Opt func(*VM) + +func WithMemory(m memory.Memory) Opt { + return func(vm *VM) { + vm.memory = m + } +} + +func WithStackSize(n uint32) Opt { + return func(vm *VM) { + vm.stack = make(stack, 0, n) + } +} + +func New(delegators map[string]delegate.Delegator, opts ...Opt) *VM { + vm := &VM{ delegators: delegators, } + + for _, opt := range opts { + opt(vm) + } + + return vm } func (v *VM) reset() { @@ -47,26 +67,26 @@ func (v *VM) checkVersion() error { return nil } -func (v *VM) terminate() (*memory.Addr, error) { +func (v *VM) terminate() (memory.Addr, error) { val, err := v.stack.pop(1) if err != nil { - return nil, err + return memory.Nil, err } return val[0], nil } -func (v *VM) Exec(bcode []byte) (*memory.Addr, error) { +func (v *VM) Exec(bcode []byte) (memory.Addr, error) { v.reset() v.prog.Init(bcode, flatbuffers.GetUOffsetT(bcode)) if err := v.checkVersion(); err != nil { - return nil, err + return memory.Nil, err } framesLen := v.prog.FramesLength() if framesLen == 0 { - return nil, errNoFrames + return memory.Nil, errNoFrames } for i := 0; i < framesLen; i++ { @@ -75,23 +95,23 @@ func (v *VM) Exec(bcode []byte) (*memory.Addr, error) { if err == errOpRet { return v.terminate() } - return nil, errors.Wrapf(err, "failed to exec frame at %d", i) + return memory.Nil, errors.Wrapf(err, "failed to exec frame at %d", i) } v.stack.push(addr) } - return nil, errUnexpectedEOP + return memory.Nil, errUnexpectedEOP } -func (v *VM) execFrame(i int) (*memory.Addr, error) { +func (v *VM) execFrame(i int) (memory.Addr, error) { var frame bytecode.Frame if !v.prog.Frames(&frame, i) { - return nil, errUnexpectedEOF + return memory.Nil, errUnexpectedEOF } var tab flatbuffers.Table if !frame.Op(&tab) { - return nil, errNoOperation + return memory.Nil, errNoOperation } opType := frame.OpType() @@ -100,45 +120,45 @@ func (v *VM) execFrame(i int) (*memory.Addr, error) { var op bytecode.OpPushBool op.Init(tab.Bytes, tab.Pos) if op.Val() { - return memory.ConstTrue, nil + return memory.True, nil } - return memory.ConstFalse, nil + return memory.False, nil case bytecode.OpOpPushStr: var op bytecode.OpPushStr op.Init(tab.Bytes, tab.Pos) - return v.memory.AllocBytesAddr(op.Val()) + return v.memory.AllocBytesAddr(op.Val()), nil case bytecode.OpOpPushInt: var op bytecode.OpPushInt op.Init(tab.Bytes, tab.Pos) - return v.memory.AllocInt64(op.Val()) + return v.memory.AllocInt64(op.Val()), nil case bytecode.OpOpPushVector: var op bytecode.OpPushVector op.Init(tab.Bytes, tab.Pos) elems, err := v.stack.pop(uint32(op.Elems())) if err != nil { - return nil, err + return memory.Nil, err } - return v.memory.CopyVector(elems...) + return v.memory.CopyVector(elems...), nil case bytecode.OpOpSysCall: var op bytecode.OpSysCall op.Init(tab.Bytes, tab.Pos) fn := op.Name() if fn == nil { - return nil, errEmptyDelegatorName + return memory.Nil, errEmptyDelegatorName } delegator, ok := v.delegators[string(fn)] if !ok { - return nil, fmt.Errorf("delegator <%s> not exists", fn) + return memory.Nil, fmt.Errorf("delegator <%s> not exists", fn) } args := op.Args() argv, err := v.stack.pop(uint32(args)) if err != nil { - return nil, err + return memory.Nil, err } return delegator.Delegate(&v.memory, argv) case bytecode.OpOpRet: - return nil, errOpRet + return memory.Nil, errOpRet default: - return nil, fmt.Errorf("unexpected frame type %s", opType) + return memory.Nil, fmt.Errorf("unexpected frame type %s", opType) } } diff --git a/exec/vm_test.go b/exec/vm_test.go index 5bc8475..f527f9b 100644 --- a/exec/vm_test.go +++ b/exec/vm_test.go @@ -259,6 +259,6 @@ func TestVM_Exec(t *testing.T) { addr, err := exec.Exec(bcode) require.EqualError(t, err, "failed to exec frame at 0: delegator not exists") - assert.Nil(t, addr) + assert.Equal(t, memory.Nil, addr) }) } diff --git a/memory/addr.go b/memory/addr.go index 8a9fd58..28df431 100644 --- a/memory/addr.go +++ b/memory/addr.go @@ -6,31 +6,32 @@ import ( ) var ( - ConstTrue = NewAddr(TypeBool, 1) - ConstFalse = NewAddr(TypeBool, 0) - ConstNoBytes = NewAddr(TypeBytes) + True = NewAddr(TypeBool, 1) + False = NewAddr(TypeBool, 0) + NoBytes = NewAddr(TypeBytes) + Nil = NewAddr(TypeNil) ) type Addr struct { typ Type dat []byte - vec []*Addr + vec []Addr } -func NewAddr(t Type, dat ...byte) *Addr { - return &Addr{ +func NewAddr(t Type, dat ...byte) Addr { + return Addr{ typ: t, dat: dat, } } -func (a *Addr) Size() uint32 { return uint32(len(a.dat)) } +func (a Addr) Size() uint32 { return uint32(len(a.dat)) } -func (a *Addr) Vector() []*Addr { +func (a Addr) Vector() []Addr { return a.vec } -func (a *Addr) CopyBytes(src ...*Addr) { +func (a Addr) CopyBytes(src ...Addr) { var offset uint32 for _, s := range src { copy(a.dat[offset:], s.dat) @@ -38,42 +39,42 @@ func (a *Addr) CopyBytes(src ...*Addr) { } } -func (a *Addr) CopyVector(v []*Addr) { +func (a Addr) CopyVector(v []Addr) { copy(a.vec, v) } -func (a *Addr) VectorAt(i int) *Addr { +func (a Addr) VectorAt(i int) Addr { return a.vec[i] } -func (a *Addr) SetVectorAt(i int, v *Addr) { +func (a Addr) SetVectorAt(i int, v Addr) { a.vec[i] = v } -func (a *Addr) VectorLen() int { +func (a Addr) VectorLen() int { return len(a.vec) } -func (a *Addr) Bytes() []byte { +func (a Addr) Bytes() []byte { return a.dat } -func (a *Addr) Int64() int64 { +func (a Addr) Int64() int64 { return int64(binary.BigEndian.Uint64(a.dat)) } -func (a *Addr) setInt64(n int64) { +func (a Addr) setInt64(n int64) { binary.BigEndian.PutUint64(a.dat, uint64(n)) } -func (a *Addr) Bool() bool { +func (a Addr) Bool() bool { return a.dat[0] == 1 } -func (a *Addr) Type() Type { +func (a Addr) Type() Type { return a.typ } -func (a *Addr) EqualBytes(b *Addr) bool { +func (a Addr) EqualBytes(b Addr) bool { return bytes.Equal(a.dat, b.dat) } diff --git a/memory/addr_test.go b/memory/addr_test.go index 3bf8ccf..ad2e66c 100644 --- a/memory/addr_test.go +++ b/memory/addr_test.go @@ -8,12 +8,12 @@ import ( "github.com/stretchr/testify/require" ) -func Test_Addr_Size(t *testing.T) { +func TestAddr_Size(t *testing.T) { addr := memory.NewAddr(memory.TypeBytes, 1, 2, 3) assert.Equal(t, uint32(3), addr.Size()) } -func Test_Addr_Bool(t *testing.T) { +func TestAddr_Bool(t *testing.T) { addrTrue := memory.NewAddr(memory.TypeBool, 1) assert.True(t, addrTrue.Bool()) @@ -21,13 +21,13 @@ func Test_Addr_Bool(t *testing.T) { assert.False(t, addrFalse.Bool()) } -func Test_Addr_NilVector(t *testing.T) { +func TestAddr_NilVector(t *testing.T) { addr := memory.NewAddr(memory.TypeVector) require.Nil(t, addr.Vector()) } -func Test_Addr_CopyBytes(t *testing.T) { +func TestAddr_CopyBytes(t *testing.T) { addr1 := memory.NewAddr(memory.TypeBytes, 1, 2, 3) addr2 := memory.NewAddr(memory.TypeBytes, 4, 5, 6) @@ -37,7 +37,7 @@ func Test_Addr_CopyBytes(t *testing.T) { assert.Equal(t, []byte{1, 2, 3, 4, 5, 6}, addrX.Bytes()) } -func Test_Addr_EqualBytes(t *testing.T) { - assert.True(t, memory.ConstTrue.EqualBytes(memory.ConstTrue)) - assert.False(t, memory.ConstTrue.EqualBytes(memory.ConstFalse)) +func TestAddr_EqualBytes(t *testing.T) { + assert.True(t, memory.True.EqualBytes(memory.True)) + assert.False(t, memory.True.EqualBytes(memory.False)) } diff --git a/memory/errors.go b/memory/errors.go deleted file mode 100644 index 8caafef..0000000 --- a/memory/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package memory - -import "errors" - -var ( - errOutOfBounds = errors.New("memory: out of bounds") - errGridOverflow = errors.New("memory: grid limit exceeded") - errLinksOverflow = errors.New("memory: links limit exceeded") - errOutOfGrid = errors.New("memory: out of grid") -) diff --git a/memory/grid.go b/memory/grid.go index 89caaf9..5770b3a 100644 --- a/memory/grid.go +++ b/memory/grid.go @@ -1,24 +1,34 @@ package memory type grid struct { - p uint32 - seq [GridLimit]Addr + off uint32 + buf []Addr } -func (g *grid) size() uint32 { - return uint32(len(g.seq)) +func (g *grid) grow(n uint32) { + l := uint32(len(g.buf)) + if g.off+n < l { + return + } + buf := make([]Addr, 2*l+n) + copy(buf, g.buf) + g.buf = buf +} + +func (g *grid) alloc(n uint32) []Addr { + g.grow(n) + off := g.off + g.off += n + return g.buf[off:g.off] } func (g *grid) reset() { - g.p = 0 + g.off = 0 } -func (g *grid) add(a Addr) (*Addr, error) { - if g.p == g.size() { - return nil, errGridOverflow - } - ref := &g.seq[g.p] - g.p++ - *ref = a - return ref, nil +func (g *grid) add(a Addr) Addr { + g.grow(1) + g.buf[g.off] = a + g.off++ + return a } diff --git a/memory/heap.go b/memory/heap.go index 5be3397..9b94375 100644 --- a/memory/heap.go +++ b/memory/heap.go @@ -1,25 +1,27 @@ package memory -import "fmt" - type heap struct { - p uint32 - buf [HeapLimit]byte + off uint32 + buf []byte } -func (h *heap) size() uint32 { - return uint32(len(h.buf)) +func (h *heap) grow(n uint32) { + l := uint32(len(h.buf)) + if h.off+n < l { + return + } + buf := make([]byte, 2*l+n) + copy(buf, h.buf) + h.buf = buf } func (h *heap) reset() { - h.p = 0 + h.off = 0 } -func (h *heap) alloc(size uint32) ([]byte, error) { - if h.p+size > h.size() { - return nil, fmt.Errorf("memory: out of memory to alloc %d bytes", h.p-h.size()+size) - } - p := h.p - h.p += size - return h.buf[p:h.p], nil +func (h *heap) alloc(n uint32) []byte { + h.grow(n) + off := h.off + h.off += n + return h.buf[off:h.off] } diff --git a/memory/limits.go b/memory/limits.go deleted file mode 100644 index 4fe0ea2..0000000 --- a/memory/limits.go +++ /dev/null @@ -1,15 +0,0 @@ -package memory - -const ( - // HeapLimit sets the maximum number of bytes. - HeapLimit uint32 = 65536 - // GridLimit sets the maximum number of addresses. - GridLimit uint32 = 1024 - // LinksLimit sets the maximum number of pointers. - LinksLimit uint32 = 1024 - - sizeInt8 = 1 - sizeInt16 = sizeInt8 << 1 - sizeInt32 = sizeInt16 << 1 - sizeInt64 = sizeInt32 << 1 -) diff --git a/memory/links.go b/memory/links.go deleted file mode 100644 index d352316..0000000 --- a/memory/links.go +++ /dev/null @@ -1,23 +0,0 @@ -package memory - -type links struct { - p uint32 - seq [LinksLimit]*Addr -} - -func (l *links) size() uint32 { - return uint32(len(l.seq)) -} - -func (l *links) reset() { - l.p = 0 -} - -func (l *links) alloc(n uint32) ([]*Addr, error) { - if l.p+n >= l.size() { - return nil, errLinksOverflow - } - p := l.p - l.p += n - return l.seq[p:l.p], nil -} diff --git a/memory/memory.go b/memory/memory.go index 99969c6..5a6a2e3 100644 --- a/memory/memory.go +++ b/memory/memory.go @@ -2,108 +2,114 @@ package memory import "fmt" +const ( + sizeInt8 = 1 + sizeInt16 = sizeInt8 << 1 + sizeInt32 = sizeInt16 << 1 + sizeInt64 = sizeInt32 << 1 +) + type Memory struct { g grid - l links h heap } -func (b *Memory) Heapfree() uint32 { - return b.h.size() - b.h.p +type Opt func(*Memory) + +func PreallocGrid(size uint32) Opt { + return func(m *Memory) { + m.g.grow(size) + } +} + +func PreallocHeap(size uint32) Opt { + return func(m *Memory) { + m.h.grow(size) + } +} + +func New(opts ...Opt) Memory { + m := Memory{} + + for _, opt := range opts { + opt(&m) + } + + return m } func (b *Memory) Reset() { b.g.reset() - b.l.reset() b.h.reset() } -func (b *Memory) alloc(t Type, size uint32) (*Addr, error) { - buf, err := b.h.alloc(size) - if err != nil { - return nil, err - } +func (b *Memory) alloc(t Type, size uint32) Addr { + buf := b.h.alloc(size) return b.allocAddr(t, buf) } -func (b *Memory) allocAddr(t Type, dat []byte) (*Addr, error) { +func (b *Memory) allocAddr(t Type, dat []byte) Addr { return b.g.add(Addr{ typ: t, dat: dat, }) } -func (b *Memory) Alloc(in interface{}) (*Addr, error) { +func (b *Memory) Alloc(in interface{}) (Addr, error) { switch v := in.(type) { case []byte: - return b.AllocBytesAddr(v) + return b.AllocBytesAddr(v), nil case string: - return b.AllocBytesAddr([]byte(v)) + return b.AllocBytesAddr([]byte(v)), nil case int: - return b.AllocInt64(int64(v)) + return b.AllocInt64(int64(v)), nil case int64: - return b.AllocInt64(v) + return b.AllocInt64(v), nil case bool: if v { - return ConstTrue, nil + return True, nil } - return ConstFalse, nil + return False, nil case []interface{}: - vec, err := b.AllocVector(uint32(len(v))) - if err != nil { - return nil, err - } + vec := b.AllocVector(uint32(len(v))) for i, e := range v { addr, err := b.Alloc(e) if err != nil { - return nil, err + return Nil, err } vec.SetVectorAt(i, addr) } return vec, nil default: - return nil, fmt.Errorf("memory: unsupported type %T", v) + return Nil, fmt.Errorf("memory: unsupported type %T", v) } } -func (b *Memory) AllocBytesAddr(dat []byte) (*Addr, error) { +func (b *Memory) AllocBytesAddr(dat []byte) Addr { return b.allocAddr(TypeBytes, dat) } -func (b *Memory) AllocBytes(size uint32) (*Addr, error) { +func (b *Memory) AllocBytes(size uint32) Addr { if size == 0 { - return ConstNoBytes, nil + return NoBytes } return b.alloc(TypeBytes, size) } -func (b *Memory) AllocInt64(v int64) (*Addr, error) { - addr, err := b.alloc(TypeInt64, sizeInt64) - if err != nil { - return nil, err - } +func (b *Memory) AllocInt64(v int64) Addr { + addr := b.alloc(TypeInt64, sizeInt64) addr.setInt64(v) - return addr, nil + return addr } -func (b *Memory) AllocVector(size uint32) (*Addr, error) { - addr, err := b.allocAddr(TypeVector, nil) - if err != nil { - return nil, err - } - vec, err := b.l.alloc(size) - if err != nil { - return nil, err - } - addr.vec = vec - return addr, nil +func (b *Memory) AllocVector(size uint32) Addr { + addr := b.allocAddr(TypeVector, nil) + addr.vec = b.g.alloc(size) + return addr } -func (b *Memory) CopyVector(elems ...*Addr) (*Addr, error) { - addr, err := b.AllocVector(uint32(len(elems))) - if err != nil { - return nil, err - } +func (b *Memory) CopyVector(elems ...Addr) Addr { + addr := b.AllocVector(uint32(len(elems))) addr.CopyVector(elems) - return addr, nil + return addr } diff --git a/memory/memory_test.go b/memory/memory_test.go index d067f70..55495ff 100644 --- a/memory/memory_test.go +++ b/memory/memory_test.go @@ -8,105 +8,45 @@ import ( "github.com/stretchr/testify/require" ) -func TestMemory_Reset(t *testing.T) { - var b memory.Memory - - assert.Equal(t, memory.HeapLimit, b.Heapfree()) - - _, err := b.AllocBytes(0xff) - require.NoError(t, err) - - assert.Equal(t, memory.HeapLimit-0xff, b.Heapfree()) - - b.Reset() - - assert.Equal(t, memory.HeapLimit, b.Heapfree()) -} +var mem memory.Memory func TestMemory_AllocBytes(t *testing.T) { t.Run("alloc zero", func(t *testing.T) { - var b memory.Memory - addr, err := b.AllocBytes(0) - require.NoError(t, err) + addr := mem.AllocBytes(0) assert.Equal(t, uint32(0), addr.Size()) }) - - t.Run("alloc bytes", func(t *testing.T) { - var b memory.Memory - - addr, err := b.AllocBytes(memory.HeapLimit) - require.NoError(t, err) - assert.Equal(t, memory.HeapLimit, addr.Size()) - }) - - t.Run("out of memory", func(t *testing.T) { - var b memory.Memory - - _, err := b.AllocBytes(memory.HeapLimit) - require.NoError(t, err) - - addr, err := b.AllocBytes(1) - assert.EqualError(t, err, "memory: out of memory to alloc 1 bytes") - assert.Nil(t, addr) - }) } func TestMemory_AllocInt64(t *testing.T) { t.Run("alloc int64", func(t *testing.T) { - var b memory.Memory - - addr, err := b.AllocInt64(0xffffff) - require.NoError(t, err) - assert.Equal(t, memory.HeapLimit-8, b.Heapfree()) + addr := mem.AllocInt64(0xffffff) assert.Equal(t, memory.TypeInt64, addr.Type()) assert.Equal(t, int64(0xffffff), addr.Int64()) }) - - t.Run("out of memory", func(t *testing.T) { - var b memory.Memory - - _, err := b.AllocBytes(memory.HeapLimit) - require.NoError(t, err) - - addr, err := b.AllocInt64(0xffffff) - require.EqualError(t, err, "memory: out of memory to alloc 8 bytes") - assert.Nil(t, addr) - }) } func TestMemory_AllocBytesAddr(t *testing.T) { - var b memory.Memory - - addr, err := b.AllocBytesAddr([]byte{1}) - require.NoError(t, err) + addr := mem.AllocBytesAddr([]byte{1}) require.NotNil(t, addr) - assert.Equal(t, memory.HeapLimit, b.Heapfree()) assert.Equal(t, memory.TypeBytes, addr.Type()) assert.Equal(t, []byte{1}, addr.Bytes()) } func TestMemory_AllocVector(t *testing.T) { t.Run("alloc vector", func(t *testing.T) { - var b memory.Memory - - addr, err := b.AllocVector(3) - require.NoError(t, err) + addr := mem.AllocVector(3) assert.Equal(t, memory.TypeVector, addr.Type()) assert.Equal(t, 3, addr.VectorLen()) }) t.Run("copy vector", func(t *testing.T) { - var b memory.Memory - - elem, err := b.AllocBytes(1) - require.NoError(t, err) + elem := mem.AllocBytes(1) - addr, err := b.CopyVector(elem, elem, elem) - require.NoError(t, err) + addr := mem.CopyVector(elem, elem, elem) require.NotNil(t, addr) assert.Equal(t, memory.TypeVector, addr.Type()) @@ -116,48 +56,11 @@ func TestMemory_AllocVector(t *testing.T) { assert.Equal(t, elem, addr.VectorAt(i)) } }) - - t.Run("links limit overflow", func(t *testing.T) { - var b memory.Memory - - addr, err := b.AllocVector(memory.LinksLimit + 1) - require.EqualError(t, err, "memory: links limit exceeded") - require.Nil(t, addr) - }) - - t.Run("copy vector error", func(t *testing.T) { - var b memory.Memory - - elems := make([]*memory.Addr, memory.LinksLimit+1) - - addr, err := b.CopyVector(elems...) - require.EqualError(t, err, "memory: links limit exceeded") - require.Nil(t, addr) - }) - - t.Run("grid limit overflow", func(t *testing.T) { - var b memory.Memory - - bytes := []byte{1} - - var i uint32 - for i = 0; i < memory.GridLimit; i++ { - addr, err := b.AllocBytesAddr(bytes) - require.NoError(t, err) - require.NotNil(t, addr) - } - - addr, err := b.AllocBytesAddr(bytes) - require.EqualError(t, err, "memory: grid limit exceeded") - require.Nil(t, addr) - }) } -func Test_Memory_Alloc(t *testing.T) { +func TestMemory_Alloc(t *testing.T) { t.Run("bytes", func(t *testing.T) { - var b memory.Memory - - addr, err := b.Alloc([]byte{1, 2, 3}) + addr, err := mem.Alloc([]byte{1, 2, 3}) require.NoError(t, err) require.NotNil(t, addr) @@ -166,9 +69,7 @@ func Test_Memory_Alloc(t *testing.T) { }) t.Run("string", func(t *testing.T) { - var b memory.Memory - - addr, err := b.Alloc("foobar") + addr, err := mem.Alloc("foobar") require.NoError(t, err) require.NotNil(t, addr) @@ -177,21 +78,17 @@ func Test_Memory_Alloc(t *testing.T) { }) t.Run("bool", func(t *testing.T) { - var b memory.Memory - - addrT, err := b.Alloc(true) + addrT, err := mem.Alloc(true) require.NoError(t, err) - assert.Equal(t, memory.ConstTrue, addrT) + assert.Equal(t, memory.True, addrT) - addrF, err := b.Alloc(false) + addrF, err := mem.Alloc(false) require.NoError(t, err) - assert.Equal(t, memory.ConstFalse, addrF) + assert.Equal(t, memory.False, addrF) }) t.Run("int", func(t *testing.T) { - var b memory.Memory - - addr, err := b.Alloc(1) + addr, err := mem.Alloc(1) require.NoError(t, err) require.NotNil(t, addr) @@ -200,9 +97,7 @@ func Test_Memory_Alloc(t *testing.T) { }) t.Run("int64", func(t *testing.T) { - var b memory.Memory - - addr, err := b.Alloc(int64(1)) + addr, err := mem.Alloc(int64(1)) require.NoError(t, err) require.NotNil(t, addr) @@ -211,9 +106,7 @@ func Test_Memory_Alloc(t *testing.T) { }) t.Run("untyped vector", func(t *testing.T) { - var b memory.Memory - - addr, err := b.Alloc([]interface{}{"foo", "bar"}) + addr, err := mem.Alloc([]interface{}{"foo", "bar"}) require.NoError(t, err) require.NotNil(t, addr) @@ -228,10 +121,8 @@ func Test_Memory_Alloc(t *testing.T) { }) t.Run("unsupported type", func(t *testing.T) { - var b memory.Memory - - addr, err := b.Alloc(struct{}{}) + addr, err := mem.Alloc(struct{}{}) require.EqualError(t, err, "memory: unsupported type struct {}") - require.Nil(t, addr) + assert.Equal(t, memory.Nil, addr) }) } diff --git a/memory/type.go b/memory/type.go index eab2639..364207a 100644 --- a/memory/type.go +++ b/memory/type.go @@ -2,13 +2,15 @@ package memory // Memory types supported by the virtual machine. const ( - TypeBytes Type = iota + TypeNil Type = iota + TypeBytes TypeInt64 TypeBool TypeVector ) var typeNames = [...]string{ + "nil", "bytes", "int64", "bool", diff --git a/memory/type_test.go b/memory/type_test.go index 574eef1..89353a7 100644 --- a/memory/type_test.go +++ b/memory/type_test.go @@ -14,6 +14,7 @@ func Test_Type_String(t *testing.T) { typ memory.Type expected string }{ + {memory.TypeNil, "nil"}, {memory.TypeBytes, "bytes"}, {memory.TypeInt64, "int64"}, {memory.TypeBool, "bool"}, diff --git a/stdlib/compare.go b/stdlib/compare.go index f6a79b3..4867b5c 100644 --- a/stdlib/compare.go +++ b/stdlib/compare.go @@ -50,29 +50,29 @@ func init() { } -func equals(mem *memory.Memory, argv []*memory.Addr) (*memory.Addr, error) { +func equals(mem *memory.Memory, argv []memory.Addr) (memory.Addr, error) { if argv[0].EqualBytes(argv[1]) { - return memory.ConstTrue, nil + return memory.True, nil } - return memory.ConstFalse, nil + return memory.False, nil } -func contains(mem *memory.Memory, argv []*memory.Addr) (*memory.Addr, error) { +func contains(mem *memory.Memory, argv []memory.Addr) (memory.Addr, error) { for _, p := range argv[0].Vector() { if p.EqualBytes(argv[1]) { - return memory.ConstTrue, nil + return memory.True, nil } } - return memory.ConstFalse, nil + return memory.False, nil } -func intersects(mem *memory.Memory, argv []*memory.Addr) (*memory.Addr, error) { +func intersects(mem *memory.Memory, argv []memory.Addr) (memory.Addr, error) { for _, a := range argv[0].Vector() { for _, b := range argv[1].Vector() { if a.EqualBytes(b) { - return memory.ConstTrue, nil + return memory.True, nil } } } - return memory.ConstFalse, nil + return memory.False, nil } diff --git a/stdlib/strings.go b/stdlib/strings.go index 4f51003..29d1e71 100644 --- a/stdlib/strings.go +++ b/stdlib/strings.go @@ -19,32 +19,26 @@ func init() { ))) } -func concat(mem *memory.Memory, argv []*memory.Addr) (*memory.Addr, error) { +func concat(mem *memory.Memory, argv []memory.Addr) (memory.Addr, error) { var size uint32 for _, a := range argv { size += a.Size() } - addr, err := mem.AllocBytes(size) - if err != nil { - return nil, err - } + addr := mem.AllocBytes(size) addr.CopyBytes(argv...) return addr, nil } -func join(mem *memory.Memory, argv []*memory.Addr) (*memory.Addr, error) { +func join(mem *memory.Memory, argv []memory.Addr) (memory.Addr, error) { sep, srcs := argv[0], argv[1].Vector() if len(srcs) == 0 { - return memory.ConstNoBytes, nil + return memory.NoBytes, nil } size := sep.Size() * uint32(len(srcs)-1) for _, src := range srcs { size += src.Size() } - addr, err := mem.AllocBytes(size) - if err != nil { - return nil, err - } + addr := mem.AllocBytes(size) buf := addr.Bytes() var offset uint32 for i, src := range srcs {