diff --git a/go.mod b/go.mod index 86fe477b..951059f7 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,19 @@ go 1.22.1 require ( github.com/consensys/gnark-crypto v0.12.1 + github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 + github.com/stretchr/testify v1.8.2 + golang.org/x/sys v0.9.0 ) require ( github.com/bits-and-blooms/bitset v1.7.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.9.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 87b61420..5aaf1df6 100644 --- a/go.sum +++ b/go.sum @@ -3,23 +3,41 @@ github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/air/eval.go b/pkg/air/eval.go index 9271ed01..5d2f26be 100644 --- a/pkg/air/eval.go +++ b/pkg/air/eval.go @@ -5,7 +5,7 @@ import ( "github.com/consensys/go-corset/pkg/table" ) -// EvalAt evaluates a column access at a given row in a trace, which returns the +// EvalAt evaluates a column access at a given row in a mmap, which returns the // value at that row of the column in question or nil is that row is // out-of-bounds. func (e *ColumnAccess) EvalAt(k int, tbl table.Trace) *fr.Element { @@ -24,7 +24,7 @@ func (e *ColumnAccess) EvalAt(k int, tbl table.Trace) *fr.Element { return clone.Set(val) } -// EvalAt evaluates a constant at a given row in a trace, which simply returns +// EvalAt evaluates a constant at a given row in a mmap, which simply returns // that constant. func (e *Constant) EvalAt(k int, tbl table.Trace) *fr.Element { var clone fr.Element @@ -32,21 +32,21 @@ func (e *Constant) EvalAt(k int, tbl table.Trace) *fr.Element { return clone.Set(e.Value) } -// EvalAt evaluates a sum at a given row in a trace by first evaluating all of +// EvalAt evaluates a sum at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Add) EvalAt(k int, tbl table.Trace) *fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Add(l, r) } return evalExprsAt(k, tbl, e.Args, fn) } -// EvalAt evaluates a product at a given row in a trace by first evaluating all of +// EvalAt evaluates a product at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Mul) EvalAt(k int, tbl table.Trace) *fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Mul(l, r) } return evalExprsAt(k, tbl, e.Args, fn) } -// EvalAt evaluates a subtraction at a given row in a trace by first evaluating all of +// EvalAt evaluates a subtraction at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Sub) EvalAt(k int, tbl table.Trace) *fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Sub(l, r) } diff --git a/pkg/air/expr.go b/pkg/air/expr.go index 8675f605..4a9d266e 100644 --- a/pkg/air/expr.go +++ b/pkg/air/expr.go @@ -10,7 +10,7 @@ import ( // Expressions at this level are split into those which can be arithmetised and // those which cannot. The latter represent expressions which cannot be // expressed within a polynomial but can be computed externally (e.g. during -// trace expansion). +// mmap expansion). type Expr interface { // EvalAt evaluates this expression in a given tabular context. Observe that // if this expression is *undefined* within this context then it returns diff --git a/pkg/binfile/constraint_set.go b/pkg/binfile/constraint_set.go index 50fdad20..00e690f5 100644 --- a/pkg/binfile/constraint_set.go +++ b/pkg/binfile/constraint_set.go @@ -21,7 +21,7 @@ type column struct { // can be assigned to the same "register". Register int // Indicates the padding value (if given) to use when padding - // out a trace for this column. + // out a mmap for this column. PaddingValue any `json:"padding_value"` // Determines whether the type was marked with "@prove" or // not. Such types must be established by corset in some way @@ -42,7 +42,7 @@ type column struct { Computed bool // Provides additional information about whether this column // is computed or not. A "Commitment" kind indicates a - // user-defined columns (i.e is directly filled from trace + // user-defined columns (i.e is directly filled from mmap // files); a "Computed" column is filled by a given function; // an "Expression" kind indicates a column whose values are // computed from an expresion known at compile time. As for diff --git a/pkg/cmd/compute.go b/pkg/cmd/compute.go index a8266e97..07702d91 100644 --- a/pkg/cmd/compute.go +++ b/pkg/cmd/compute.go @@ -12,8 +12,8 @@ import ( // computeCmd represents the compute command var computeCmd = &cobra.Command{ Use: "compute", - Short: "Given a set of constraints and a trace file, fill the computed columns.", - Long: `Given a set of constraints and a trace file, fill the computed columns.`, + Short: "Given a set of constraints and a mmap file, fill the computed columns.", + Long: `Given a set of constraints and a mmap file, fill the computed columns.`, Run: func(cmd *cobra.Command, args []string) { file := args[0] fmt.Printf("Reading JSON bin file: %s\n", file) diff --git a/pkg/hir/eval.go b/pkg/hir/eval.go index e096c669..1acfd404 100644 --- a/pkg/hir/eval.go +++ b/pkg/hir/eval.go @@ -5,7 +5,7 @@ import ( "github.com/consensys/go-corset/pkg/table" ) -// EvalAllAt evaluates a column access at a given row in a trace, which returns the +// EvalAllAt evaluates a column access at a given row in a mmap, which returns the // value at that row of the column in question or nil is that row is // out-of-bounds. func (e *ColumnAccess) EvalAllAt(k int, tbl table.Trace) []*fr.Element { @@ -23,7 +23,7 @@ func (e *ColumnAccess) EvalAllAt(k int, tbl table.Trace) []*fr.Element { return []*fr.Element{clone.Set(val)} } -// EvalAllAt evaluates a constant at a given row in a trace, which simply returns +// EvalAllAt evaluates a constant at a given row in a mmap, which simply returns // that constant. func (e *Constant) EvalAllAt(k int, tbl table.Trace) []*fr.Element { var clone fr.Element @@ -31,21 +31,21 @@ func (e *Constant) EvalAllAt(k int, tbl table.Trace) []*fr.Element { return []*fr.Element{clone.Set(e.Val)} } -// EvalAllAt evaluates a sum at a given row in a trace by first evaluating all of +// EvalAllAt evaluates a sum at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Add) EvalAllAt(k int, tbl table.Trace) []*fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Add(l, r) } return evalExprsAt(k, tbl, e.Args, fn) } -// EvalAllAt evaluates a product at a given row in a trace by first evaluating all of +// EvalAllAt evaluates a product at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Mul) EvalAllAt(k int, tbl table.Trace) []*fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Mul(l, r) } return evalExprsAt(k, tbl, e.Args, fn) } -// EvalAllAt evaluates a conditional at a given row in a trace by first evaluating +// EvalAllAt evaluates a conditional at a given row in a mmap by first evaluating // its condition at that row. If that condition is zero then the true branch // (if applicable) is evaluated; otherwise if the condition is non-zero then // false branch (if applicable) is evaluated). If the branch to be evaluated is @@ -66,7 +66,7 @@ func (e *IfZero) EvalAllAt(k int, tbl table.Trace) []*fr.Element { return vals } -// EvalAllAt evaluates a list at a given row in a trace by evaluating each of its +// EvalAllAt evaluates a list at a given row in a mmap by evaluating each of its // arguments at that row. func (e *List) EvalAllAt(k int, tbl table.Trace) []*fr.Element { vals := make([]*fr.Element, 0) @@ -95,7 +95,7 @@ func (e *Normalise) EvalAllAt(k int, tbl table.Trace) []*fr.Element { return vals } -// EvalAllAt evaluates a subtraction at a given row in a trace by first evaluating all of +// EvalAllAt evaluates a subtraction at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Sub) EvalAllAt(k int, tbl table.Trace) []*fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Sub(l, r) } diff --git a/pkg/hir/schema.go b/pkg/hir/schema.go index d8d47ce0..6bf2ba8f 100644 --- a/pkg/hir/schema.go +++ b/pkg/hir/schema.go @@ -132,8 +132,8 @@ func (p *Schema) AddPropertyAssertion(handle string, expr mir.Expr) { p.assertions = append(p.assertions, table.NewPropertyAssertion[mir.Expr](handle, expr)) } -// Accepts determines whether this schema will accept a given trace. That -// is, whether or not the given trace adheres to the schema. A trace can fail +// Accepts determines whether this schema will accept a given mmap. That +// is, whether or not the given mmap adheres to the schema. A mmap can fail // to adhere to the schema for a variety of reasons, such as having a constraint // which does not hold. func (p *Schema) Accepts(trace table.Trace) error { @@ -157,7 +157,7 @@ func (p *Schema) Accepts(trace table.Trace) error { return nil } -// ExpandTrace expands a given trace according to this schema. +// ExpandTrace expands a given mmap according to this schema. func (p *Schema) ExpandTrace(tr table.Trace) error { // Insert initial padding row table.PadTrace(1, tr) diff --git a/pkg/mir/eval.go b/pkg/mir/eval.go index b4237908..41c6de74 100644 --- a/pkg/mir/eval.go +++ b/pkg/mir/eval.go @@ -5,7 +5,7 @@ import ( "github.com/consensys/go-corset/pkg/table" ) -// EvalAt evaluates a column access at a given row in a trace, which returns the +// EvalAt evaluates a column access at a given row in a mmap, which returns the // value at that row of the column in question or nil is that row is // out-of-bounds. func (e *ColumnAccess) EvalAt(k int, tbl table.Trace) *fr.Element { @@ -23,7 +23,7 @@ func (e *ColumnAccess) EvalAt(k int, tbl table.Trace) *fr.Element { return clone.Set(val) } -// EvalAt evaluates a constant at a given row in a trace, which simply returns +// EvalAt evaluates a constant at a given row in a mmap, which simply returns // that constant. func (e *Constant) EvalAt(k int, tbl table.Trace) *fr.Element { var clone fr.Element @@ -31,7 +31,7 @@ func (e *Constant) EvalAt(k int, tbl table.Trace) *fr.Element { return clone.Set(e.Value) } -// EvalAt evaluates a sum at a given row in a trace by first evaluating all of +// EvalAt evaluates a sum at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Add) EvalAt(k int, tbl table.Trace) *fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Add(l, r) } @@ -59,7 +59,7 @@ func (e *Normalise) EvalAt(k int, tbl table.Trace) *fr.Element { return val } -// EvalAt evaluates a subtraction at a given row in a trace by first evaluating all of +// EvalAt evaluates a subtraction at a given row in a mmap by first evaluating all of // its arguments at that row. func (e *Sub) EvalAt(k int, tbl table.Trace) *fr.Element { fn := func(l *fr.Element, r *fr.Element) { l.Sub(l, r) } diff --git a/pkg/mir/schema.go b/pkg/mir/schema.go index 83ce36aa..971ed268 100644 --- a/pkg/mir/schema.go +++ b/pkg/mir/schema.go @@ -98,8 +98,8 @@ func (p *Schema) AddPropertyAssertion(handle string, expr Expr) { p.assertions = append(p.assertions, table.NewPropertyAssertion(handle, expr)) } -// Accepts determines whether this schema will accept a given trace. That -// is, whether or not the given trace adheres to the schema. A trace can fail +// Accepts determines whether this schema will accept a given mmap. That +// is, whether or not the given mmap adheres to the schema. A mmap can fail // to adhere to the schema for a variety of reasons, such as having a constraint // which does not hold. func (p *Schema) Accepts(trace table.Trace) error { @@ -176,7 +176,7 @@ func lowerColumnToAir(c *table.DataColumn[table.Type], schema *air.Schema) { // Lower a permutation to the AIR level. This has quite a few // effects. Firstly, permutation constraints are added for all of the // new columns. Secondly, sorting constraints (and their associated -// synthetic columns) must also be added. Finally, a trace +// synthetic columns) must also be added. Finally, a mmap // computation is required to ensure traces are correctly expanded to // meet the requirements of a sorted permutation. func lowerPermutationToAir(c Permutation, mirSchema *Schema, airSchema *air.Schema) { @@ -215,7 +215,7 @@ func lowerPermutationToAir(c Permutation, mirSchema *Schema, airSchema *air.Sche } } -// ExpandTrace expands a given trace according to this schema. +// ExpandTrace expands a given mmap according to this schema. func (p *Schema) ExpandTrace(tr table.Trace) error { // Insert initial padding row table.PadTrace(1, tr) diff --git a/pkg/mmap/block_device.go b/pkg/mmap/block_device.go new file mode 100644 index 00000000..a2797474 --- /dev/null +++ b/pkg/mmap/block_device.go @@ -0,0 +1,99 @@ +package mmap + +import ( + "errors" + "io" + "runtime/debug" + "syscall" + + pkgErrors "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +// BlockDevice represents a mmap block device holding a reference to a file descriptor. +type BlockDevice struct { + FileDescriptor int + Data []byte +} + +// NewBlockDevice creates a BlockDevice from a file +// descriptor referring either to a regular file or UNIX device node. To +// speed up reads, a memory map is used. +func NewBlockDevice(fileDescriptor, sizeBytes int) (*BlockDevice, error) { + data, err := unix.Mmap(fileDescriptor, 0, sizeBytes, syscall.PROT_READ, syscall.MAP_SHARED) + if err != nil { + return nil, pkgErrors.Wrap(err, "failed to memory map block device") + } + + return &BlockDevice{ + FileDescriptor: fileDescriptor, + Data: data, + }, nil +} + +// ReadAt reads through the memory map at a given offset. +func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + // Let read actions go through the memory map to prevent system + // call overhead for commonly requested objects. + if off < 0 { + return 0, syscall.EINVAL + } + + if off > int64(len(bd.Data)) { + return 0, io.EOF + } + // Install a page fault handler, so that I/O errors against the + // memory map (e.g., due to disk failure) don't cause us to + // crash. + old := debug.SetPanicOnFault(true) + defer func() { + debug.SetPanicOnFault(old) + + if recover() != nil { + err = errors.New("page fault occurred while reading from memory map") + } + }() + + n = copy(p, bd.Data[off:]) + if n < len(p) { + err = io.EOF + } + + return +} + +// WriteAt writes at a given offset. +func (bd *BlockDevice) WriteAt(p []byte, off int64) (int, error) { + // Let write actions go through the file descriptor. Doing so + // yields better performance, as writes through a memory map + // would trigger a page fault that causes data to be read. + // + // The pwrite() system call cannot return a size and error at + // the same time. If an error occurs after one or more bytes are + // written, it returns the size without an error (a "short + // write"). As WriteAt() must return an error in those cases, we + // must invoke pwrite() repeatedly. + // + // TODO: Maybe it makes sense to let unaligned writes that would + // trigger reads anyway to go through the memory map? + nTotal := 0 + + for len(p) > 0 { + n, err := unix.Pwrite(bd.FileDescriptor, p, off) + nTotal += n + + if err != nil { + return nTotal, err + } + + p = p[n:] + off += int64(n) + } + + return nTotal, nil +} + +// Sync synchronizes a file's in-core state with storage device. +func (bd *BlockDevice) Sync() error { + return unix.Fsync(bd.FileDescriptor) +} diff --git a/pkg/mmap/file.go b/pkg/mmap/file.go new file mode 100644 index 00000000..b40e490f --- /dev/null +++ b/pkg/mmap/file.go @@ -0,0 +1,49 @@ +package mmap + +import ( + pkgErrors "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +// File represents a memory-mapped file. +type File struct { + BlockDevice *BlockDevice + SectorSizeBytes int + SectorCount int64 +} + +// NewFile constructs a new instance of File. +func NewFile(path string, minimumSizeBytes int) (*File, error) { + fd, err := unix.Open(path, unix.O_CREAT|unix.O_RDWR|unix.O_APPEND, 0666) + if err != nil { + return nil, pkgErrors.Wrapf(err, "failed to open file %#v", path) + } + + // Use the block size returned by fstat() to determine the + // sector size and the number of sectors needed to store the + // desired amount of space. + var stat unix.Stat_t + if err := unix.Fstat(fd, &stat); err != nil { + return nil, pkgErrors.Wrapf(err, "failed to obtain size of file %#v", path) + } + + sectorSizeBytes := int(stat.Blksize) + sectorCount := int64((uint64(minimumSizeBytes) + uint64(stat.Blksize) - 1) / uint64(stat.Blksize)) + sizeBytes := int64(sectorSizeBytes) * sectorCount + + if err := unix.Ftruncate(fd, sizeBytes); err != nil { + return nil, pkgErrors.Wrapf(err, "failed to truncate file %#v to %d bytes", path, sizeBytes) + } + + bd, err := NewBlockDevice(fd, int(sizeBytes)) + + if err := unix.Close(fd); err != nil { + return nil, err + } + + return &File{ + BlockDevice: bd, + SectorSizeBytes: sectorSizeBytes, + SectorCount: sectorCount, + }, nil +} diff --git a/pkg/mmap/file_test.go b/pkg/mmap/file_test.go new file mode 100644 index 00000000..b2f003f1 --- /dev/null +++ b/pkg/mmap/file_test.go @@ -0,0 +1,75 @@ +package mmap_test + +import ( + "os" + "path/filepath" + "runtime/debug" + "testing" + + "github.com/consensys/go-corset/pkg/mmap" + + "github.com/stretchr/testify/require" +) + +func TestNewBlockDeviceFromFile(t *testing.T) { + minSizeBytes := 123456 + blockDevicePath := filepath.Join(t.TempDir(), "test_blockdevice") + + println(blockDevicePath) + + mmapFile, err := mmap.NewFile(blockDevicePath, minSizeBytes) + require.NoError(t, err) + + sectorSizeBytes := mmapFile.SectorSizeBytes + sectorCount := mmapFile.SectorCount + blockDevice := mmapFile.BlockDevice + // The sector size should be a power of two, and the number of + // sectors should be sufficient to hold the required space. + require.LessOrEqual(t, 512, sectorSizeBytes) + require.Equal(t, 0, sectorSizeBytes&(sectorSizeBytes-1)) + require.Equal(t, int64((minSizeBytes+sectorSizeBytes-1)/sectorSizeBytes), sectorCount) + + // The file on disk should have a size that corresponds to the + // sector size and count. + fileInfo, err := os.Stat(blockDevicePath) + require.NoError(t, err) + require.Equal(t, int64(sectorSizeBytes)*sectorCount, fileInfo.Size()) + + // Test read, write and sync operations. + n, err := blockDevice.WriteAt([]byte("Hello"), 12345) + require.Equal(t, 5, n) + require.NoError(t, err) + + var b [16]byte + n, err = blockDevice.ReadAt(b[:], 12340) + require.Equal(t, 16, n) + require.NoError(t, err) + require.Equal(t, []byte("\x00\x00\x00\x00\x00Hello\x00\x00\x00\x00\x00\x00"), b[:]) + + require.NoError(t, mmapFile.BlockDevice.Sync()) + + // Truncating the file will cause future read access to the + // memory map underneath the BlockDevice to raise SIGBUS. This + // may also occur in case of actual I/O errors. These page + // faults should be caught properly. + // + // To be able to implement this, ReadAt() temporary enables the + // debug.SetPanicOnFault() option. Test that the original value + // of this option is restored upon completion. + require.NoError(t, os.Truncate(blockDevicePath, 0)) + + debug.SetPanicOnFault(false) + + n, _ = blockDevice.ReadAt(b[:], 12340) + + require.False(t, debug.SetPanicOnFault(false)) + require.Equal(t, 0, n) + + debug.SetPanicOnFault(true) + + n, err = blockDevice.ReadAt(b[:], 12340) + + require.True(t, debug.SetPanicOnFault(false)) + require.Equal(t, 0, n) + require.Error(t, err, "page fault occurred while reading from memory map") +} diff --git a/pkg/table/column.go b/pkg/table/column.go index 03d86c84..00053516 100644 --- a/pkg/table/column.go +++ b/pkg/table/column.go @@ -30,7 +30,7 @@ func (c *DataColumn[T]) Get(row int, tr Trace) *fr.Element { return tr.GetByName(c.Name, row) } -// Accepts determines whether or not this column accepts the given trace. For a +// Accepts determines whether this column accepts the given mmap. For a // data column, this means ensuring that all elements are value for the columns // type. // @@ -66,20 +66,20 @@ func (c *DataColumn[T]) String() string { // ComputedColumn describes a column whose values are computed on-demand, rather // than being stored in a data array. Typically computed columns read values -// from other columns in a trace in order to calculate their value. There is an +// from other columns in a mmap in order to calculate their value. There is an // expectation that this computation is acyclic. Furthermore, computed columns -// give rise to "trace expansion". That is where the initial trace provided by +// give rise to "mmap expansion". That is where the initial mmap provided by // the user is expanded by determining the value of all computed columns. type ComputedColumn struct { Name string - // The computation which accepts a given trace and computes + // The computation which accepts a given mmap and computes // the value of this column at a given row. Expr Evaluable } // NewComputedColumn constructs a new computed column with a given name and // determining expression. More specifically, that expression is used to -// compute the values for this column during trace expansion. +// compute the values for this column during mmap expansion. func NewComputedColumn(name string, expr Evaluable) *ComputedColumn { return &ComputedColumn{ Name: name, @@ -101,7 +101,7 @@ func (c *ComputedColumn) Accepts(tr Trace) error { return nil } -// ExpandTrace attempts to a new column to the trace which contains the result +// ExpandTrace attempts to a new column to the mmap which contains the result // of evaluating a given expression on each row. If the column already exists, // then an error is flagged. func (c *ComputedColumn) ExpandTrace(tr Trace) error { @@ -111,7 +111,7 @@ func (c *ComputedColumn) ExpandTrace(tr Trace) error { } data := make([]*fr.Element, tr.Height()) - // Expand the trace + // Expand the mmap for i := 0; i < len(data); i++ { val := c.Expr.EvalAt(i, tr) if val != nil { @@ -121,7 +121,7 @@ func (c *ComputedColumn) ExpandTrace(tr Trace) error { data[i] = &zero } } - // Colunm needs to be expanded. + // Column needs to be expanded. tr.AddColumn(c.Name, data) // Done return nil @@ -231,7 +231,7 @@ func (p *SortedPermutation) Accepts(tr Trace) error { return errors.New(msg) } -// ExpandTrace expands a given trace to include the columns specified by a given +// ExpandTrace expands a given mmap to include the columns specified by a given // SortedPermutation. This requires copying the data in the source columns, and // sorting that data according to the permutation criteria. func (p *SortedPermutation) ExpandTrace(tr Trace) error { @@ -293,7 +293,7 @@ func (p *SortedPermutation) String() string { } // IsPermutationOf checks whether (or not) one column is a permutation -// of another in given trace. The order in which columns are given is +// of another in given mmap. The order in which columns are given is // not important. func IsPermutationOf(target string, source string, tr Trace) error { dst := tr.ColumnByName(target) diff --git a/pkg/table/constraints.go b/pkg/table/constraints.go index 1fdde338..48b702bc 100644 --- a/pkg/table/constraints.go +++ b/pkg/table/constraints.go @@ -104,7 +104,7 @@ func (p *RowConstraint[T]) Accepts(tr Trace) error { } // HoldsGlobally checks whether a given expression vanishes (i.e. evaluates to -// zero) for all rows of a trace. If not, report an appropriate error. +// zero) for all rows of a mmap. If not, report an appropriate error. func HoldsGlobally[T Testable](handle string, constraint T, tr Trace) error { for k := 0; k < tr.Height(); k++ { if err := HoldsLocally(k, handle, constraint, tr); err != nil { @@ -116,9 +116,9 @@ func HoldsGlobally[T Testable](handle string, constraint T, tr Trace) error { } // HoldsLocally checks whether a given constraint holds (e.g. vanishes) on a -// specific row of a trace. If not, report an appropriate error. +// specific row of a mmap. If not, report an appropriate error. func HoldsLocally[T Testable](k int, handle string, constraint T, tr Trace) error { - // Negative rows calculated from end of trace. + // Negative rows calculated from end of mmap. if k < 0 { k += tr.Height() } @@ -214,7 +214,7 @@ func (p *RangeConstraint) String() string { // PropertyAssertion is similar to a vanishing constraint but is used only for // debugging / testing / verification. Unlike vanishing constraints, property // assertions do not represent something that the prover can enforce. Rather, -// they represent properties which are expected to hold for every valid trace. +// they represent properties which are expected to hold for every valid mmap. // That is, they should be implied by the actual constraints. Thus, whilst the // prover cannot enforce such properties, external tools (such as for formal // verification) can attempt to ensure they do indeed always hold. @@ -223,9 +223,9 @@ type PropertyAssertion[E Evaluable] struct { // useful for debugging. Handle string // The actual assertion itself, namely an expression which - // should hold (i.e. vanish) for every row of a trace. + // should hold (i.e. vanish) for every row of a mmap. // Observe that this can be any function which is computable - // on a given trace --- we are not restricted to expressions + // on a given mmap --- we are not restricted to expressions // which can be arithmetised. Expr E } diff --git a/pkg/test/ir_test.go b/pkg/test/ir_test.go index 503c9253..aaaa0f1d 100644 --- a/pkg/test/ir_test.go +++ b/pkg/test/ir_test.go @@ -401,6 +401,7 @@ func checkExpandedTrace(t *testing.T, tr table.Trace, id traceId, schema table.S msg := fmt.Sprintf("Trace accepted incorrectly (%s, %s.rejects, %d padding, line %d)", id.ir, id.test, id.padding, id.line) + t.Errorf(msg) } }