aboutsummaryrefslogtreecommitdiff
path: root/pkg/lang/vm/mem
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-05-28 20:56:10 +0000
committerGitHub <noreply@github.com>2022-05-28 20:56:10 +0000
commita8e90423a6d29d7241b7d1f5ab60d607edffef9f (patch)
tree5580f186ba5ac1283a0fce97654d633ddf63beff /pkg/lang/vm/mem
parent2ddb215e3b0d3818b3fac8f315d97d8020eb699f (diff)
downloadjinx-a8e90423a6d29d7241b7d1f5ab60d607edffef9f.tar.zst
jinx-a8e90423a6d29d7241b7d1f5ab60d607edffef9f.zip
Harden Mem further and add tests
Diffstat (limited to 'pkg/lang/vm/mem')
-rw-r--r--pkg/lang/vm/mem/cell.go1
-rw-r--r--pkg/lang/vm/mem/errors.go27
-rw-r--r--pkg/lang/vm/mem/mem.go26
-rw-r--r--pkg/lang/vm/mem/mem_test.go238
4 files changed, 284 insertions, 8 deletions
diff --git a/pkg/lang/vm/mem/cell.go b/pkg/lang/vm/mem/cell.go
index 830d382..562d4da 100644
--- a/pkg/lang/vm/mem/cell.go
+++ b/pkg/lang/vm/mem/cell.go
@@ -40,4 +40,5 @@ type cell struct {
type CellData interface {
DropCell(Mem)
+ MatchingCellKind() CellKind
}
diff --git a/pkg/lang/vm/mem/errors.go b/pkg/lang/vm/mem/errors.go
index e7a3f8f..f5dcc86 100644
--- a/pkg/lang/vm/mem/errors.go
+++ b/pkg/lang/vm/mem/errors.go
@@ -1,17 +1,36 @@
package mem
-import "errors"
+import (
+ "errors"
+ "fmt"
+)
var (
ErrMemOverflow = errors.New("memory overflow, cannot allocate more than 10000 memory cells")
-
- ErrFatalNonFreeCell = errors.New("non-free cell marked as free")
)
+type ErrInvalidCellKind struct {
+ Kind CellKind
+}
+
+func (e ErrInvalidCellKind) Error() string {
+ return fmt.Sprintf("cannot allocate cell of kind %s", e.Kind)
+}
+
type ErrInvalidMemAccess struct {
Ptr Ptr
}
func (e ErrInvalidMemAccess) Error() string {
- return "invalid memory access at " + e.Ptr.String()
+ return fmt.Sprintf("invalid memory access at %d", e.Ptr)
+}
+
+type ErrDifferingCellKind struct {
+ Ptr Ptr
+ Expected CellKind
+ Got CellKind
+}
+
+func (e ErrDifferingCellKind) Error() string {
+ return fmt.Sprintf("tried assigning %v to mem cell of type %v at %d", e.Got, e.Expected, e.Ptr)
}
diff --git a/pkg/lang/vm/mem/mem.go b/pkg/lang/vm/mem/mem.go
index a645ee2..0f15743 100644
--- a/pkg/lang/vm/mem/mem.go
+++ b/pkg/lang/vm/mem/mem.go
@@ -10,6 +10,7 @@ type Mem interface {
Retain(ptr Ptr) error
Release(ptr Ptr) error
+ RefCount(ptr Ptr) int
}
type memImpl struct {
@@ -30,15 +31,20 @@ func New() Mem {
}
func (m *memImpl) Allocate(kind CellKind) (Ptr, error) {
+ if kind == CellKindForbidden || kind == CellKindEmpty {
+ return NullPtr, ErrInvalidCellKind{kind}
+ }
+
if len(m.free) > 0 {
idx := m.free[len(m.free)-1]
m.free = m.free[:len(m.free)-1]
if m.cells[idx].kind != CellKindEmpty {
- return NullPtr, ErrFatalNonFreeCell
+ // This should never happen.
+ panic("cell marked as free was not empty")
}
- m.cells[idx].kind = kind
+ m.cells[idx] = cell{kind: kind, refs: 1}
return Ptr(idx), nil
} else {
if len(m.cells) > 10000 {
@@ -57,6 +63,10 @@ func (m *memImpl) Set(ptr Ptr, v CellData) error {
return err
}
+ if m.cells[ptr].kind != v.MatchingCellKind() {
+ return ErrDifferingCellKind{Ptr: ptr, Expected: m.cells[ptr].kind, Got: v.MatchingCellKind()}
+ }
+
m.cells[ptr].data = v
return nil
}
@@ -71,7 +81,7 @@ func (m *memImpl) Get(ptr Ptr) (CellData, error) {
func (m *memImpl) Is(ptr Ptr, kind CellKind) bool {
if ptr >= Ptr(len(m.cells)) {
- return false
+ return kind == CellKindEmpty
}
return m.cells[ptr].kind == kind
@@ -79,7 +89,7 @@ func (m *memImpl) Is(ptr Ptr, kind CellKind) bool {
func (m *memImpl) Kind(ptr Ptr) CellKind {
if ptr >= Ptr(len(m.cells)) {
- return CellKindForbidden
+ return CellKindEmpty
}
return m.cells[ptr].kind
@@ -111,6 +121,14 @@ func (m *memImpl) Release(ptr Ptr) error {
return nil
}
+func (m *memImpl) RefCount(ptr Ptr) int {
+ if err := m.validPtr(ptr); err != nil {
+ return 0
+ }
+
+ return m.cells[ptr].refs
+}
+
func (m *memImpl) validPtr(ptr Ptr) error {
if ptr >= Ptr(len(m.cells)) {
return ErrInvalidMemAccess{ptr}
diff --git a/pkg/lang/vm/mem/mem_test.go b/pkg/lang/vm/mem/mem_test.go
new file mode 100644
index 0000000..a75753c
--- /dev/null
+++ b/pkg/lang/vm/mem/mem_test.go
@@ -0,0 +1,238 @@
+package mem_test
+
+import (
+ "jinx/pkg/lang/vm/mem"
+ "jinx/pkg/lang/vm/value"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAllocation(t *testing.T) {
+ m := mem.New()
+
+ one, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+
+ two, err := m.Allocate(mem.CellKindArray)
+ assert.NoError(t, err)
+
+ assert.Equal(t, one, mem.Ptr(1))
+ assert.Equal(t, two, mem.Ptr(2))
+}
+
+func TestGetSet(t *testing.T) {
+ m := mem.New()
+
+ one, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.NoError(t, m.Set(one, value.StringCell("one")))
+
+ two, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.NoError(t, m.Set(two, value.StringCell("two")))
+
+ oneCell, err := m.Get(one)
+ assert.NoError(t, err)
+ assert.Equal(t, oneCell.(value.StringCell).Get(), "one")
+
+ twoCell, err := m.Get(two)
+ assert.NoError(t, err)
+ assert.Equal(t, twoCell.(value.StringCell).Get(), "two")
+
+ assert.NoError(t, m.Set(one, value.StringCell("one, but changed")))
+
+ oneCell, err = m.Get(one)
+ assert.NoError(t, err)
+ assert.Equal(t, oneCell.(value.StringCell).Get(), "one, but changed")
+}
+
+func TestNullIsForbidden(t *testing.T) {
+ m := mem.New()
+
+ _, err := m.Get(mem.NullPtr)
+ assert.Error(t, err)
+
+ assert.Error(t, m.Set(mem.NullPtr, value.StringCell("one")))
+
+ assert.Error(t, m.Retain(mem.NullPtr))
+ assert.Error(t, m.Release(mem.NullPtr))
+ assert.Equal(t, 0, m.RefCount(mem.NullPtr))
+}
+
+func TestCantGetNorSetUnallocatedCell(t *testing.T) {
+ m := mem.New()
+
+ _, err := m.Get(mem.Ptr(123))
+ assert.Error(t, err)
+
+ err = m.Set(mem.Ptr(123), value.StringCell("one"))
+ assert.Error(t, err)
+}
+
+func TestCantAllocateMarkerCells(t *testing.T) {
+ m := mem.New()
+
+ _, err := m.Allocate(mem.CellKindEmpty)
+ assert.Error(t, err)
+
+ _, err = m.Allocate(mem.CellKindForbidden)
+ assert.Error(t, err)
+}
+
+func TestCantAssignWrongCellKind(t *testing.T) {
+ m := mem.New()
+
+ one, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.Error(t, m.Set(one, value.EnvCell(value.NewEnv())))
+}
+
+func TestIsAndKind(t *testing.T) {
+ m := mem.New()
+
+ one, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+
+ assert.True(t, m.Is(one, mem.CellKindString))
+ assert.False(t, m.Is(one, mem.CellKindArray))
+
+ assert.Equal(t, mem.CellKindString, m.Kind(one))
+}
+
+func TestIsAndKindAlwaysAgree(t *testing.T) {
+ m := mem.New()
+
+ _, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ _, err = m.Allocate(mem.CellKindArray)
+ assert.NoError(t, err)
+ _, err = m.Allocate(mem.CellKindOutlet)
+ assert.NoError(t, err)
+ _, err = m.Allocate(mem.CellKindEnv)
+ assert.NoError(t, err)
+
+ for i := 0; i < 100; i++ {
+ ptr := mem.Ptr(i)
+ assert.True(t, m.Is(ptr, m.Kind(ptr)))
+ }
+}
+
+func TestRetainRelease(t *testing.T) {
+ m := mem.New()
+
+ ptr, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.NoError(t, m.Set(ptr, value.StringCell("an allocated string")))
+
+ assert.True(t, m.Is(ptr, mem.CellKindString))
+ assert.Equal(t, 1, m.RefCount(ptr))
+
+ assert.NoError(t, m.Retain(ptr))
+ assert.NoError(t, m.Retain(ptr))
+ assert.NoError(t, m.Retain(ptr))
+
+ assert.True(t, m.Is(ptr, mem.CellKindString))
+ assert.Equal(t, 4, m.RefCount(ptr))
+
+ assert.NoError(t, m.Release(ptr))
+ assert.NoError(t, m.Release(ptr))
+ assert.NoError(t, m.Release(ptr))
+
+ assert.True(t, m.Is(ptr, mem.CellKindString))
+ cell, err := m.Get(ptr)
+ assert.NoError(t, err)
+ assert.Equal(t, "an allocated string", cell.(value.StringCell).Get())
+ assert.Equal(t, 1, m.RefCount(ptr))
+
+ assert.NoError(t, m.Release(ptr))
+
+ assert.Equal(t, 0, m.RefCount(ptr))
+ assert.True(t, m.Is(ptr, mem.CellKindEmpty))
+ _, err = m.Get(ptr)
+ assert.Error(t, err)
+}
+
+func TestCantReleaseTooMuch(t *testing.T) {
+ m := mem.New()
+
+ ptr, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.NoError(t, m.Set(ptr, value.StringCell("an allocated string")))
+
+ assert.NoError(t, m.Retain(ptr))
+ assert.Equal(t, 2, m.RefCount(ptr))
+
+ assert.NoError(t, m.Release(ptr))
+ assert.NoError(t, m.Release(ptr))
+ assert.Error(t, m.Release(ptr))
+}
+
+func TestCantRetainNorReleaseUnallocated(t *testing.T) {
+ m := mem.New()
+
+ assert.Error(t, m.Retain(mem.Ptr(123)))
+ assert.Error(t, m.Release(mem.Ptr(123)))
+}
+
+func TestChainDrop(t *testing.T) {
+ m := mem.New()
+
+ arr := []value.Value{
+ unwrap(t, func() (value.Value, error) { return value.NewString(m, "one") }),
+ unwrap(t, func() (value.Value, error) { return value.NewString(m, "two") }),
+
+ unwrap(t, func() (value.Value, error) {
+ return value.NewArray(m, []value.Value{
+ unwrap(t, func() (value.Value, error) { return value.NewString(m, "three") }),
+ })
+ }),
+ }
+
+ val, err := value.NewArray(m, arr)
+ assert.NoError(t, err)
+
+ assert.Equal(t, mem.CellKindString, m.Kind(mem.Ptr(1)))
+ assert.Equal(t, mem.CellKindString, m.Kind(mem.Ptr(2)))
+ assert.Equal(t, mem.CellKindString, m.Kind(mem.Ptr(3)))
+ assert.Equal(t, mem.CellKindArray, m.Kind(mem.Ptr(4)))
+ assert.Equal(t, mem.CellKindArray, m.Kind(mem.Ptr(5)))
+ assert.Equal(t, mem.CellKindEmpty, m.Kind(mem.Ptr(6)))
+
+ val.Drop(m)
+
+ for _, ptr := range []mem.Ptr{1, 2, 3, 4, 5, 6} {
+ assert.Equal(t, mem.CellKindEmpty, m.Kind(ptr))
+ }
+}
+
+func TestFreeCellsGetReused(t *testing.T) {
+ m := mem.New()
+
+ one, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.NoError(t, m.Set(one, value.StringCell("old one")))
+
+ two, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.NoError(t, m.Set(two, value.StringCell("old two")))
+
+ assert.NoError(t, m.Release(one))
+
+ newOne, err := m.Allocate(mem.CellKindString)
+ assert.NoError(t, err)
+ assert.NoError(t, m.Set(newOne, value.StringCell("new one")))
+
+ assert.Equal(t, mem.CellKindString, m.Kind(newOne))
+ newCell, err := m.Get(newOne)
+ assert.NoError(t, err)
+ assert.Equal(t, "new one", newCell.(value.StringCell).Get())
+ assert.Equal(t, 1, m.RefCount(newOne))
+ assert.Equal(t, one, newOne)
+}
+
+func unwrap[X any](t *testing.T, f func() (X, error)) X {
+ x, err := f()
+ assert.NoError(t, err)
+ return x
+}