Skip to content

Commit d52971c

Browse files
authored
support resizeable read/write memory (#1701)
* support resizeable read/write memory This tweaks the way in which read/write memory handles out-of-bounds accesses (i.e. reads). Previously, this would panic. However, now it returns 0. * minor fix * minor tidying up * linting
1 parent 5276056 commit d52971c

11 files changed

Lines changed: 139 additions & 88 deletions

File tree

pkg/test/zkc_unit_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ func Test_ZkcUnit_Basic_19(t *testing.T) {
9696
checkZkcUnit(t, "zkc/unit/basic_19")
9797
}
9898

99+
func Test_ZkcUnit_Basic_20(t *testing.T) {
100+
checkZkcUnit(t, "zkc/unit/basic_20")
101+
}
102+
99103
// ===================================================================
100104
// If-Else-If Tests
101105
// ===================================================================

pkg/zkc/vm/machine/base.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,6 @@ func (p *Base[W]) Modules() []Module[W] {
129129
return p.modules
130130
}
131131

132-
// Read implementation of machine.Core interface
133-
func (p *Base[W]) Read(id uint, address []W) (data []W) {
134-
var rm = p.modules[id].(memory.Memory[W])
135-
// Perform read
136-
return rm.Read(address)
137-
}
138-
139-
// Write implementation of machine.Core interface
140-
func (p *Base[W]) Write(id uint, address []W, data []W) {
141-
var wm = p.modules[id].(memory.Memory[W])
142-
// Perform write
143-
wm.Write(address, data)
144-
}
145-
146132
// Depth returns the depth of the call stack.
147133
func (p *Base[W]) Depth() uint {
148134
return uint(len(p.callstack))
@@ -296,12 +282,12 @@ func (p *Base[W]) executeInstruction(insn instruction.MicroInstruction[W], width
296282
case *instruction.MemRead[W]:
297283
var rom = p.modules[insn.Id].(memory.Memory[W])
298284
// Read data words from tiven address
299-
err = rom.FrameRead(frame, insn.Address, insn.Data)
285+
err = rom.Read(frame, insn.Address, insn.Data)
300286
// Fall thru
301287
case *instruction.MemWrite[W]:
302288
var rom = p.modules[insn.Id].(memory.Memory[W])
303289
// Read data words from tiven address
304-
err = rom.FrameWrite(frame, insn.Address, insn.Data)
290+
err = rom.Write(frame, insn.Address, insn.Data)
305291
// Fall thru
306292

307293
// ==============================================================

pkg/zkc/vm/machine/core.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,6 @@ type Core[W any] interface {
6161
// true if the last frame was popped off the stack (i.e. the machine has
6262
// terminated).
6363
Leave() bool
64-
// Read location from ith module. This must be a readable memory, otherwise
65-
// this will panic.
66-
Read(id uint, address []W) (data []W)
67-
// Write location in ith module. This must be a writeable memory, otherwise
68-
// this will panic.
69-
Write(id uint, address []W, data []W)
7064
}
7165

7266
// Module represents an either a function or memory within the machine.

pkg/zkc/vm/memory/dynamic_array.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright Consensys Software Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
4+
// the License. You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
9+
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
10+
// specific language governing permissions and limitations under the License.
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
package memory
14+
15+
import (
16+
"github.com/consensys/go-corset/pkg/schema/register"
17+
"github.com/consensys/go-corset/pkg/zkc/vm/word"
18+
)
19+
20+
// DynamicArray is a memory implementation backed by a dynamically sizing []W,
21+
// meaning that an out-of-bound read will return 0. Reads are performed by
22+
// delegating address decoding to a D (an AddressDecoder) which translates the
23+
// incoming multi-word address tuple into a (start, end) index range, and then
24+
// returning the corresponding sub-slice of the backing data.
25+
//
26+
// The type parameter W is the word type (e.g. a field element or big.Int), and
27+
// D is the AddressDecoder strategy that encodes the layout of rows within the
28+
// flat slice.
29+
type DynamicArray[W word.Word[W]] struct {
30+
StaticArray[W]
31+
}
32+
33+
// newDynamicArray constructs a new array initialised with a given set of values.
34+
func newDynamicArray[W word.Word[W]](name string, registers []register.Register, init ...W) DynamicArray[W] {
35+
var geometry = NewGeometry[W](registers)
36+
//
37+
return DynamicArray[W]{StaticArray[W]{geometry, name, init}}
38+
}
39+
40+
// Read implementation for Memory interface.
41+
func (p *DynamicArray[W]) Read(frame []W, address []register.Id, data []register.Id) error {
42+
var start, _ = p.geometry.FrameDecode(frame, address)
43+
//
44+
for i := range data {
45+
frame[data[i].Unwrap()] = p.read(uint64(i) + start)
46+
}
47+
//
48+
return nil
49+
}
50+
51+
// Internal read function handles out-of-bounds accesses.
52+
func (p *DynamicArray[W]) read(address uint64) W {
53+
if address < uint64(len(p.data)) {
54+
return p.data[address]
55+
}
56+
// out-of-bounds access
57+
var zero W
58+
//
59+
return zero
60+
}

pkg/zkc/vm/memory/memory.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ type Memory[W word.Word[W]] interface {
2929
// Initialise this memory with the given contents. This will overwrite any
3030
// existing contents.
3131
Initialise(contents []W)
32-
// Read a given data-tuple from a given address-tuple.
33-
Read(address []W) []W
34-
// Write a given data-tuple to a given address-tuple, overwriting the
35-
// previous value stored at that address.
36-
Write(address []W, value []W)
37-
// Read a given data-tuple from a given address-tuple.
38-
FrameRead(frame []W, address []register.Id, data []register.Id) error
39-
// Read a given data-tuple from a given address-tuple.
40-
FrameWrite(frame []W, address []register.Id, data []register.Id) error
32+
// Read (indirect) a given data-tuple from a given address-tuple. The
33+
// address tuple is formed from the "frame" (i.e. the register-file) using
34+
// the given register identifiers and, likewise, the target registers are
35+
// given in data.
36+
Read(frame []W, address []register.Id, data []register.Id) error
37+
// Write (indirect) a given data-tuple at a given address-tuple. The
38+
// address tuple is formed from the "frame" (i.e. the register-file) using
39+
// the given register identifiers and, likewise, the source registers are
40+
// given in data.
41+
Write(frame []W, address []register.Id, data []register.Id) error
4142
// Return the contents of this memory as a sequence of words, where all rows
4243
// are simply appended together.
4344
Contents() []W

pkg/zkc/vm/memory/ram.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ import (
2222
// dynamically to include any cell which is written, where cells are initialised
2323
// with zero.
2424
type RandomAccess[W word.Word[W]] struct {
25-
Array[W]
25+
DynamicArray[W]
2626
}
2727

2828
// NewRandomAccess constructs an empty random-access memory.
2929
func NewRandomAccess[W word.Word[W]](name string, registers []register.Register) *RandomAccess[W] {
3030
return &RandomAccess[W]{
31-
newArray[W](name, registers),
31+
newDynamicArray[W](name, registers),
3232
}
3333
}

pkg/zkc/vm/memory/rom.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,17 @@ import (
2929
// to fixed tables used within the program (e.g. in a hash function such as
3030
// BLAKE or KECCAK, there are fixed lookup tables used as part of the program).
3131
type ReadOnly[W word.Word[W]] struct {
32-
Array[W]
32+
StaticArray[W]
3333
}
3434

3535
// NewReadOnly constructs a new read-only memory initialised with a given set of values.
3636
func NewReadOnly[W word.Word[W]](name string, registers []register.Register, init ...W) *ReadOnly[W] {
3737
return &ReadOnly[W]{
38-
newArray[W](name, registers, init...),
38+
newStaticArray[W](name, registers, init...),
3939
}
4040
}
4141

4242
// Write implementation for Memory interface.
43-
func (p *ReadOnly[W]) Write(address []W, data []W) {
44-
panic("unsupported operation for read-only memory")
45-
}
46-
47-
// FrameWrite implementation for Memory interface.
48-
func (p *ReadOnly[W]) FrameWrite(frame []W, address []register.Id, data []register.Id) error {
43+
func (p *ReadOnly[W]) Write(frame []W, address []register.Id, data []register.Id) error {
4944
panic("unsupported operation for read-only memory")
5045
}
Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,52 +17,45 @@ import (
1717
"github.com/consensys/go-corset/pkg/zkc/vm/word"
1818
)
1919

20-
// Array is a flat-slice implementation of ReadOnlyMemory backed by a []W.
21-
// Reads are performed by delegating address decoding to a D (an AddressDecoder)
22-
// which translates the incoming multi-word address tuple into a (start, end)
23-
// index range, and then returning the corresponding sub-slice of the backing
24-
// data.
20+
// StaticArray is a memory implementation backed by a fixed-size []W, meaning
21+
// that an out-of-bound read will panic. Reads are performed by delegating
22+
// address decoding to a D (an AddressDecoder) which translates the incoming
23+
// multi-word address tuple into a (start, end) index range, and then returning
24+
// the corresponding sub-slice of the backing data.
2525
//
2626
// The type parameter W is the word type (e.g. a field element or big.Int), and
2727
// D is the AddressDecoder strategy that encodes the layout of rows within the
2828
// flat slice.
29-
type Array[W word.Word[W]] struct {
29+
type StaticArray[W word.Word[W]] struct {
3030
geometry Geometry[W]
3131
name string
3232
data []W
3333
}
3434

35-
// newArray constructs a new array initialised with a given set of values.
36-
func newArray[W word.Word[W]](name string, registers []register.Register, init ...W) Array[W] {
35+
// newStaticArray constructs a new array initialised with a given set of values.
36+
func newStaticArray[W word.Word[W]](name string, registers []register.Register, init ...W) StaticArray[W] {
3737
var geometry = NewGeometry[W](registers)
3838
//
39-
return Array[W]{geometry, name, init}
39+
return StaticArray[W]{geometry, name, init}
4040
}
4141

4242
// Name implementation for Memory interface.
43-
func (p *Array[W]) Name() string {
43+
func (p *StaticArray[W]) Name() string {
4444
return p.name
4545
}
4646

4747
// Initialise implementation for Memory interface.
48-
func (p *Array[W]) Initialise(contents []W) {
48+
func (p *StaticArray[W]) Initialise(contents []W) {
4949
p.data = contents
5050
}
5151

5252
// Geometry implementation for Memory interface.
53-
func (p *Array[W]) Geometry() Geometry[W] {
53+
func (p *StaticArray[W]) Geometry() Geometry[W] {
5454
return p.geometry
5555
}
5656

5757
// Read implementation for Memory interface.
58-
func (p *Array[W]) Read(address []W) []W {
59-
var start, end = p.geometry.Decode(address)
60-
//
61-
return p.data[start:end]
62-
}
63-
64-
// FrameRead implementation for Memory interface.
65-
func (p *Array[W]) FrameRead(frame []W, address []register.Id, data []register.Id) error {
58+
func (p *StaticArray[W]) Read(frame []W, address []register.Id, data []register.Id) error {
6659
var start, _ = p.geometry.FrameDecode(frame, address)
6760
//
6861
for i := range data {
@@ -72,8 +65,8 @@ func (p *Array[W]) FrameRead(frame []W, address []register.Id, data []register.I
7265
return nil
7366
}
7467

75-
// FrameWrite implementation for Memory interface.
76-
func (p *Array[W]) FrameWrite(frame []W, address []register.Id, data []register.Id) error {
68+
// Write implementation for Memory interface.
69+
func (p *StaticArray[W]) Write(frame []W, address []register.Id, data []register.Id) error {
7770
var (
7871
n = uint64(len(p.data))
7972
start, end = p.geometry.FrameDecode(frame, address)
@@ -92,23 +85,7 @@ func (p *Array[W]) FrameWrite(frame []W, address []register.Id, data []register.
9285
return nil
9386
}
9487

95-
// Write implementation for Memory interface.
96-
func (p *Array[W]) Write(address []W, data []W) {
97-
var (
98-
n = uint64(len(p.data))
99-
start, end = p.geometry.Decode(address)
100-
)
101-
// expand memory if needed
102-
if n <= end {
103-
ndata := make([]W, end)
104-
copy(ndata, p.data)
105-
p.data = ndata
106-
}
107-
//
108-
copy(p.data[start:end], data)
109-
}
110-
11188
// Contents implementation for Memory interface.
112-
func (p *Array[W]) Contents() []W {
89+
func (p *StaticArray[W]) Contents() []W {
11390
return p.data
11491
}

pkg/zkc/vm/memory/wom.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,17 @@ import (
2222
// starting from zero. Thus, a WOM can be viewed as an output stream (which is
2323
// exactly what they are typically used for).
2424
type WriteOnce[W word.Word[W]] struct {
25-
Array[W]
25+
StaticArray[W]
2626
}
2727

2828
// NewWriteOnce constructs an empty write-once memory.
2929
func NewWriteOnce[W word.Word[W]](name string, registers []register.Register) *WriteOnce[W] {
3030
return &WriteOnce[W]{
31-
newArray[W](name, registers),
31+
newStaticArray[W](name, registers),
3232
}
3333
}
3434

3535
// Read implementation for Memory interface.
36-
func (p *WriteOnce[W]) Read(address []W) []W {
37-
panic("unsupported operation for write-once memory")
38-
}
39-
40-
// FrameRead implementation for Memory interface.
41-
func (p *WriteOnce[W]) FrameRead(frame []W, address []register.Id, data []register.Id) error {
36+
func (p *WriteOnce[W]) Read(frame []W, address []register.Id, data []register.Id) error {
4237
panic("unsupported operation for write-once memory")
4338
}

testdata/zkc/unit/basic_20.accepts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{ "data": "0x0302aa" }
2+
{ "data": "0x030200" }
3+
{ "data": "0x0403aaaa" }
4+
{ "data": "0x0f000f000000000000000000000000" }
5+
{ "data": "0x0f0101000000000000000000000000" }
6+
;;
7+
{ "data": "0x0f0200000000000000000000000000" }
8+
{ "data": "0x0f02000100000ff00000008000aa00" }
9+
{ "data": "0x0f020001000ff00000008000aa00ee" }
10+
{ "data": "0x0f020001000ff000008000aa00eeff" }
11+
;;
12+
{ "data": "0x0f0300000000000000000000000000" }
13+
{ "data": "0x0f030000aabbccddee000000000000" }
14+
{ "data": "0x0f030000aabbccddee112233445566" }
15+
;;
16+
{ "data": "0x0f04aa00aabbccddee112233445566" }
17+
{ "data": "0x0f06cc00aabbccddee112233445566" }
18+
{ "data": "0x0f0a2200aabbccddee112233445566" }
19+
;;
20+
{ "data": "0x0f0f0000aabbccddee112233445566" }

0 commit comments

Comments
 (0)