Skip to content

Commit

Permalink
Replace GlobalScope and ModuleScope
Browse files Browse the repository at this point in the history
This replaces these two constructs with a single new construct (called,
helpfully, `ModuleScope`).  The difference is that the new construct can
describe nested trees, thus making it suitable for handling perspectives
(and later submodules).

At this stage, register allocation is now working again with
perspectives.  I've chosen to retain backwards compatibility in terms of
naming with the original corset.  The issue is around the naming of
registers which arise from coalescing perspective columns.  The names
currently do not include the perspective name, and this means they can
potentially clash.  However, clashing is not a critical issue at this
time, since it will always result in the generated Trace.java file
containing a syntax error.
  • Loading branch information
DavePearce committed Jan 8, 2025
1 parent 2d37d7e commit 819f808
Show file tree
Hide file tree
Showing 23 changed files with 773 additions and 366 deletions.
30 changes: 20 additions & 10 deletions pkg/corset/allocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ func (r *Register) Deactivate() {
// RegisterSource provides necessary information about source-level columns
// allocated to a given register.
type RegisterSource struct {
// Context is a prefix of name which, when they differ, indicates a virtual
// column (i.e. one which is subject to register allocation).
context util.Path
// Fully qualified (i.e. absolute) name of source-level column.
name util.Path
// Length multiplier of source-level column.
Expand All @@ -89,6 +92,19 @@ type RegisterSource struct {
computed bool
}

// IsVirtual indicates whether or not this is a "virtual" column. That is,
// something which is subject to register allocation (i.e. because it is
// declared in a perspective).
func (p *RegisterSource) IsVirtual() bool {
return !p.name.Parent().Equals(p.context)
}

// Perspective returns the name of the "virtual perspective" in which this
// column exists.
func (p *RegisterSource) Perspective() string {
return p.name.Parent().Slice(p.context.Depth()).String()
}

// RegisterAllocation is a generic interface to support different "regsiter
// allocation" algorithms. More specifically, register allocation is the
// process of allocating columns to their underlying HIR columns (a.k.a
Expand Down Expand Up @@ -186,13 +202,8 @@ func NewRegisterAllocator(allocation RegisterAllocation) *RegisterAllocator {
if len(regInfo.Sources) != 1 {
// This should be unreachable.
panic("register not associated with unique column")
} else if regSource.name.Depth() == 0 || regSource.name.Depth() > 3 {
// This should be unreachable.
panic(fmt.Sprintf("register has invalid depth %d", regSource.name.Depth()))
} else if regSource.name.Depth() == 3 {
// FIXME: assuming names have a depth of at most three is not ideal.
perspective := regSource.name.Parent().String()
allocator.allocatePerspective(perspective)
} else if regSource.IsVirtual() {
allocator.allocatePerspective(regSource.Perspective())
}
}
// Initial allocation of perspective registers
Expand All @@ -201,9 +212,8 @@ func NewRegisterAllocator(allocation RegisterAllocation) *RegisterAllocator {
regInfo := allocation.Register(regIndex)
regSource := regInfo.Sources[0]
//
if regSource.name.Depth() == 3 {
// FIXME: assuming names have a depth of at most three is not ideal.
perspective := regSource.name.Parent().String()
if regSource.IsVirtual() {
perspective := regSource.Perspective()
allocator.allocateRegister(perspective, regIndex)
}
}
Expand Down
24 changes: 8 additions & 16 deletions pkg/corset/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,6 @@ import (
"github.com/consensys/go-corset/pkg/util"
)

// BindingId is an identifier is used to distinguish different forms of binding,
// as some forms are known from their use. Specifically, at the current time,
// only functions are distinguished from other categories (e.g. columns,
// parameters, etc).
type BindingId struct {
// Name of the binding
name string
// Indicates whether function binding or other.
fn bool
}

// Binding represents an association between a name, as found in a source file,
// and concrete item (e.g. a column, function, etc).
type Binding interface {
Expand Down Expand Up @@ -146,6 +135,11 @@ func (p *FunctionSignature) Apply(args []Expr, srcmap *sexp.SourceMaps[Node]) Ex

// ColumnBinding represents something bound to a given column.
type ColumnBinding struct {
// Context determines the real (i.e. non-virtual) enclosing module of this
// column, and should always be a prefix of the path. If this column was
// declared in a perspective then it will be the perspective's enclosing
// module. Otherwise, it will exactly match the path's parent.
context util.Path
// Absolute path of column. This determines the name of the column, its
// enclosing module and/or perspective.
path util.Path
Expand All @@ -165,8 +159,8 @@ type ColumnBinding struct {
// definterleaved constraint the target column information (e.g. its type) is
// not immediately available and must be determined from those columns from
// which it is constructed.
func NewComputedColumnBinding(path util.Path) *ColumnBinding {
return &ColumnBinding{path, true, false, 0, nil}
func NewComputedColumnBinding(context util.Path, path util.Path) *ColumnBinding {
return &ColumnBinding{context, path, true, false, 0, nil}
}

// AbsolutePath returns the fully resolved (absolute) path of the column in question.
Expand All @@ -188,9 +182,7 @@ func (p *ColumnBinding) Finalise(multiplier uint, datatype Type) {
// Context returns the of this column. That is, the module in which this colunm
// was declared and also the length multiplier of that module it requires.
func (p *ColumnBinding) Context() Context {
// FIXME: this will fail for perspectives?
module := p.path.Parent().String()
return tr.NewContext(module, p.multiplier)
return tr.NewContext(p.context.String(), p.multiplier)
}

// ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion pkg/corset/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (p *Compiler) Compile() (*hir.Schema, []SyntaxError) {
return nil, errs
}
// Convert global scope into an environment by allocating all columns.
environment := scope.ToEnvironment(p.allocator)
environment := NewGlobalEnvironment(scope, p.allocator)
// Finally, translate everything and add it to the schema.
return TranslateCircuit(environment, p.srcmap, &p.circuit)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/corset/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ func (e *DefConstUnit) Binding() Binding {
// Name returns the (unqualified) name of this symbol. For example, "X" for
// a column X defined in a module m1.
func (e *DefConstUnit) Name() string {
return e.binding.path.Head()
return e.binding.path.Tail()
}

// Path returns the qualified name (i.e. absolute path) of this symbol. For
Expand Down Expand Up @@ -781,7 +781,7 @@ type DefPerspective struct {
// Name returns the (unqualified) name of this symbol. For example, "X" for
// a column X defined in a module m1.
func (p *DefPerspective) Name() string {
return p.symbol.Path().Head()
return p.symbol.Path().Tail()
}

// Path returns the qualified name (i.e. absolute path) of this symbol. For
Expand Down Expand Up @@ -956,7 +956,7 @@ func (p *DefFun) Binding() Binding {
// Name returns the (unqualified) name of this symbol. For example, "X" for
// a column X defined in a module m1.
func (p *DefFun) Name() string {
return p.symbol.Path().Head()
return p.symbol.Path().Tail()
}

// Path returns the qualified name (i.e. absolute path) of this symbol. For
Expand Down
108 changes: 58 additions & 50 deletions pkg/corset/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,14 @@ type Environment interface {
ContextOf(from Context) tr.Context
}

// ColumnId uniquely identifiers a Corset column. Note, however, that
// multiple Corset columns can be mapped to a single underlying register.
type ColumnId struct {
module string
column string
}

// GlobalEnvironment is a wrapper around a global scope. The point, really, is
// to signal the change between a global scope whose columns have yet to be
// allocated, from an environment whose columns are allocated.
type GlobalEnvironment struct {
// Info about modules
modules map[string]*ModuleInfo
// Map source-level columns to registers
columns map[ColumnId]uint
columns map[string]uint
// Registers
registers []Register
}
Expand All @@ -67,10 +60,18 @@ type GlobalEnvironment struct {
// by allocating appropriate identifiers to all columns. This process is
// parameterised upon a given register allocator, thus enabling different
// allocation algorithms.
func NewGlobalEnvironment(scope *GlobalScope, allocator func(RegisterAllocation)) GlobalEnvironment {
func NewGlobalEnvironment(root *ModuleScope, allocator func(RegisterAllocation)) GlobalEnvironment {
// Sanity Check
if !root.IsRoot() {
// Definitely should be unreachable.
panic("root scope required")
}
// Construct top-level module list.
modules := root.Flattern()
// Initialise the environment
env := GlobalEnvironment{nil, nil, nil}
env.initModules(scope)
env.initColumnsAndRegisters(scope)
env.initModules(modules)
env.initColumnsAndRegisters(modules)
// Apply register allocation.
env.applyRegisterAllocation(allocator)
// Done
Expand All @@ -92,12 +93,7 @@ func (p GlobalEnvironment) Register(index uint) *Register {
// RegisterOf identifies the register (i.e. underlying (HIR) column) to
// which a given source-level (i.e. corset) column is allocated.
func (p GlobalEnvironment) RegisterOf(column *util.Path) uint {
// FIXME: this is broken
module := column.Parent().String()
name := column.Tail()
// Construct column identifier.
cid := ColumnId{module, name}
regId := p.columns[cid]
regId := p.columns[column.String()]
// Lookup register info
return regId
}
Expand All @@ -119,8 +115,8 @@ func (p GlobalEnvironment) RegistersOf(module string) []uint {
}

// ColumnsOf returns the set of registers allocated to a given column.
func (p GlobalEnvironment) ColumnsOf(register uint) []ColumnId {
var columns []ColumnId
func (p GlobalEnvironment) ColumnsOf(register uint) []string {
var columns []string
//
for col, reg := range p.columns {
if reg == register {
Expand All @@ -146,47 +142,51 @@ func (p GlobalEnvironment) ContextOf(from Context) tr.Context {
// Module allocation is a simple process of allocating modules their specific
// identifiers. This has to match exactly how the translator does it, otherwise
// there will be problems.
func (p *GlobalEnvironment) initModules(scope *GlobalScope) {
func (p *GlobalEnvironment) initModules(modules []*ModuleScope) {
p.modules = make(map[string]*ModuleInfo)
moduleId := uint(0)
// Allocate modules one-by-one
for _, m := range scope.modules {
name := m.path.String()
p.modules[name] = &ModuleInfo{name, moduleId}
moduleId++
// Allocate submodules one-by-one
for _, m := range modules {
if !m.virtual {
name := m.path.String()
p.modules[name] = &ModuleInfo{name, moduleId}
moduleId++
}
}
}

// Performs an initial register allocation which simply maps every column to a
// unique register. The intention is that, subsequently, registers can be
// merged as necessary.
func (p *GlobalEnvironment) initColumnsAndRegisters(scope *GlobalScope) {
p.columns = make(map[ColumnId]uint)
func (p *GlobalEnvironment) initColumnsAndRegisters(modules []*ModuleScope) {
p.columns = make(map[string]uint)
p.registers = make([]Register, 0)
// Allocate input columns first.
for _, m := range scope.modules {
for _, m := range modules {
owner := m.Owner()
//
for _, b := range m.bindings {
if binding, ok := b.(*ColumnBinding); ok && !binding.computed {
p.allocateColumn(binding)
p.allocateColumn(binding, owner.path)
}
}
}
// Allocate assignments second.
for _, m := range scope.modules {
for _, m := range modules {
owner := m.Owner()
//
for _, b := range m.bindings {
if binding, ok := b.(*ColumnBinding); ok && binding.computed {
p.allocateColumn(binding)
p.allocateColumn(binding, owner.path)
}
}
}
// Apply aliases
for _, m := range scope.modules {
name := m.path.String()
//
for _, m := range modules {
for id, binding_id := range m.ids {
if binding, ok := m.bindings[binding_id].(*ColumnBinding); ok && !id.fn {
orig := ColumnId{name, binding.path.Tail()}
alias := ColumnId{name, id.name}
orig := binding.path.String()
alias := m.path.Extend(id.name).String()
p.columns[alias] = p.columns[orig]
}
}
Expand All @@ -197,44 +197,52 @@ func (p *GlobalEnvironment) initColumnsAndRegisters(scope *GlobalScope) {
// column can correspond to multiple underling registers, this can result in the
// allocation of a number of registers (based on the columns type). For
// example, an array of length n will allocate n registers, etc.
func (p *GlobalEnvironment) allocateColumn(column *ColumnBinding) {
p.allocate(column, &column.path, column.dataType)
func (p *GlobalEnvironment) allocateColumn(column *ColumnBinding, context util.Path) {
p.allocate(column, context, column.path, column.dataType)
}

func (p *GlobalEnvironment) allocate(column *ColumnBinding, path *util.Path, datatype Type) {
func (p *GlobalEnvironment) allocate(column *ColumnBinding, ctx util.Path, path util.Path, datatype Type) {
// Check for base base
if datatype.AsUnderlying() != nil {
p.allocateUnit(column, path, datatype.AsUnderlying())
p.allocateUnit(column, ctx, path, datatype.AsUnderlying())
} else if arraytype, ok := datatype.(*ArrayType); ok {
// For now, assume must be an array
p.allocateArray(column, path, arraytype)
p.allocateArray(column, ctx, path, arraytype)
} else {
panic(fmt.Sprintf("unknown type encountered: %v", datatype))
}
}

// Allocate an array type
func (p *GlobalEnvironment) allocateArray(column *ColumnBinding, path *util.Path, arraytype *ArrayType) {
func (p *GlobalEnvironment) allocateArray(col *ColumnBinding, ctx util.Path, path util.Path, arrtype *ArrayType) {
// Allocate n columns
for i := arraytype.min; i <= arraytype.max; i++ {
for i := arrtype.min; i <= arrtype.max; i++ {
ith_name := fmt.Sprintf("%s_%d", path.Tail(), i)
ith_path := path.Parent().Extend(ith_name)
p.allocate(column, ith_path, arraytype.element)
p.allocate(col, ctx, *ith_path, arrtype.element)
}
}

// Allocate a single register.
func (p *GlobalEnvironment) allocateUnit(column *ColumnBinding, path *util.Path, datatype sc.Type) {
// FIXME: following is broken because we lose perspective information.
module := path.Parent().String()
func (p *GlobalEnvironment) allocateUnit(column *ColumnBinding, ctx util.Path, path util.Path, datatype sc.Type) {
module := ctx.String()
// // The name is extracted from the different between the context and the //
// // path. The context must be a prefix of the path and, essentially, //
// // identifies the concrete module where this column will eventually live.
// name := path.Slice(ctx.Depth()).String()[1:]
// // Neaten the name up a bit
// name = strings.ReplaceAll(name, "/", "$")
//
// FIXME: below is used instead of above in order to replicate the original
// Corset tool. Eventually, this behaviour should be deprecated.
name := path.Tail()
//
moduleId := p.modules[module].Id
colId := ColumnId{module, name}
regId := uint(len(p.registers))
// Construct appropriate register source.
source := RegisterSource{
*path,
ctx,
path,
column.multiplier,
datatype,
column.mustProve,
Expand All @@ -247,7 +255,7 @@ func (p *GlobalEnvironment) allocateUnit(column *ColumnBinding, path *util.Path,
[]RegisterSource{source},
})
// Map column to register
p.columns[colId] = regId
p.columns[path.String()] = regId
}

// Apply the given register allocator to each module of this environment in turn.
Expand Down
4 changes: 2 additions & 2 deletions pkg/corset/intrinsics.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (p *IntrinsicDefinition) Name() string {
// Path returns the qualified name (i.e. absolute path) of this symbol. For
// example, "m1.X" for a column X defined in module m1.
func (p *IntrinsicDefinition) Path() *util.Path {
path := util.NewRelativePath([]string{p.name})
path := util.NewAbsolutePath(p.name)
return &path
}

Expand Down Expand Up @@ -129,7 +129,7 @@ func intrinsicNaryBody(arity uint) []Expr {
//
for i := uint(0); i != arity; i++ {
name := fmt.Sprintf("x%d", i)
path := util.NewAbsolutePath([]string{name})
path := util.NewAbsolutePath(name)
binding := &LocalVariableBinding{name, nil, i}
args[i] = &VariableAccess{path, true, binding}
}
Expand Down
Loading

0 comments on commit 819f808

Please sign in to comment.