diff --git a/pkg/runners/cairo_runner.go b/pkg/runners/cairo_runner.go new file mode 100644 index 00000000..a3d76600 --- /dev/null +++ b/pkg/runners/cairo_runner.go @@ -0,0 +1,87 @@ +package runners + +import ( + "github.com/lambdaclass/cairo-vm.go/pkg/vm" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +type CairoRunner struct { + Program vm.Program + Vm vm.VirtualMachine + ProgramBase memory.Relocatable + executionBase memory.Relocatable + initialPc memory.Relocatable + initialAp memory.Relocatable + initialFp memory.Relocatable + finalPc memory.Relocatable + mainOffset uint +} + +func NewCairoRunner(program vm.Program) *CairoRunner { + // TODO: Fetch main entrypoint offset from program identifiers + // Placeholder + main_offset := uint(0) + return &CairoRunner{Program: program, Vm: *vm.NewVirtualMachine(), mainOffset: main_offset} + +} + +// Performs the initialization step, returns the end pointer (pc upon which execution should stop) +func (r *CairoRunner) Initialize() (memory.Relocatable, error) { + r.initializeSegments() + end, err := r.initializeMainEntrypoint() + r.initializeVM() + return end, err +} + +// Creates program, execution and builtin segments +func (r *CairoRunner) initializeSegments() { + // Program Segment + r.ProgramBase = r.Vm.Segments.AddSegment() + // Execution Segment + r.executionBase = r.Vm.Segments.AddSegment() + // Initialize builtin segments +} + +// Initializes the program segment & initial pc +func (r *CairoRunner) initializeState(entrypoint uint, stack *[]memory.MaybeRelocatable) error { + r.initialPc = r.ProgramBase + r.initialPc.Offset += entrypoint + // Load program data + _, err := r.Vm.Segments.LoadData(r.ProgramBase, &r.Program.Data) + if err == nil { + _, err = r.Vm.Segments.LoadData(r.executionBase, stack) + } + // Mark data segment as accessed + return err +} + +// Initializes memory, initial register values & returns the end pointer (final pc) to run from a given pc offset +// (entrypoint) +func (r *CairoRunner) initializeFunctionEntrypoint(entrypoint uint, stack *[]memory.MaybeRelocatable, return_fp memory.Relocatable) (memory.Relocatable, error) { + end := r.Vm.Segments.AddSegment() + *stack = append(*stack, *memory.NewMaybeRelocatableRelocatable(end), *memory.NewMaybeRelocatableRelocatable(return_fp)) + r.initialFp = r.executionBase + r.initialFp.Offset += uint(len(*stack)) + r.initialAp = r.initialFp + r.finalPc = end + return end, r.initializeState(entrypoint, stack) +} + +// Initializes memory, initial register values & returns the end pointer (final pc) to run from the main entrypoint +func (r *CairoRunner) initializeMainEntrypoint() (memory.Relocatable, error) { + // When running from main entrypoint, only up to 11 values will be written (9 builtin bases + end + return_fp) + stack := make([]memory.MaybeRelocatable, 0, 11) + // Append builtins initial stack to stack + // Handle proof-mode specific behaviour + return_fp := r.Vm.Segments.AddSegment() + return r.initializeFunctionEntrypoint(r.mainOffset, &stack, return_fp) +} + +// Initializes the vm's run_context, adds builtin validation rules & validates memory +func (r *CairoRunner) initializeVM() { + r.Vm.RunContext.Ap = r.initialAp + r.Vm.RunContext.Fp = r.initialFp + r.Vm.RunContext.Pc = r.initialPc + // Add validation rules + // Apply validation rules to memory +} diff --git a/pkg/runners/cairo_runner_test.go b/pkg/runners/cairo_runner_test.go new file mode 100644 index 00000000..bc2f0cb8 --- /dev/null +++ b/pkg/runners/cairo_runner_test.go @@ -0,0 +1,136 @@ +package runners_test + +import ( + "testing" + + "github.com/lambdaclass/cairo-vm.go/pkg/runners" + "github.com/lambdaclass/cairo-vm.go/pkg/vm" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestInitializeRunnerNoBuiltinsNoProofModeEmptyProgram(t *testing.T) { + // Create a Program with empty data + program_data := make([]memory.MaybeRelocatable, 0) + program := vm.Program{Data: program_data} + // Create CairoRunner + runner := runners.NewCairoRunner(program) + // Initialize the runner + end_ptr, err := runner.Initialize() + if err != nil { + t.Errorf("Initialize error in test: %s", err) + } + if end_ptr.SegmentIndex != 3 || end_ptr.Offset != 0 { + t.Errorf("Wrong end ptr value, got %+v", end_ptr) + } + + // Check CairoRunner values + if runner.ProgramBase.SegmentIndex != 0 || runner.ProgramBase.Offset != 0 { + t.Errorf("Wrong ProgramBase value, got %+v", runner.ProgramBase) + } + + // Check Vm's RunContext values + if runner.Vm.RunContext.Pc.SegmentIndex != 0 || runner.Vm.RunContext.Pc.Offset != 0 { + t.Errorf("Wrong Pc value, got %+v", runner.Vm.RunContext.Pc) + } + if runner.Vm.RunContext.Ap.SegmentIndex != 1 || runner.Vm.RunContext.Ap.Offset != 2 { + t.Errorf("Wrong Ap value, got %+v", runner.Vm.RunContext.Ap) + } + if runner.Vm.RunContext.Fp.SegmentIndex != 1 || runner.Vm.RunContext.Fp.Offset != 2 { + t.Errorf("Wrong Fp value, got %+v", runner.Vm.RunContext.Fp) + } + + // Check memory + + // Program segment + // 0:0 program_data[0] should be empty + value, err := runner.Vm.Segments.Memory.Get(memory.Relocatable{SegmentIndex: 0, Offset: 0}) + if err == nil { + t.Errorf("Expected addr 0:0 to be empty for empty program, got: %+v", value) + } + + // Execution segment + // 1:0 end_ptr + value, err = runner.Vm.Segments.Memory.Get(memory.Relocatable{SegmentIndex: 1, Offset: 0}) + if err != nil { + t.Errorf("Memory Get error in test: %s", err) + } + rel, ok := value.GetRelocatable() + if !ok || rel.SegmentIndex != 3 || rel.Offset != 0 { + t.Errorf("Wrong value for address 1:0: %d", rel) + } + // 1:1 return_fp + value, err = runner.Vm.Segments.Memory.Get(memory.Relocatable{SegmentIndex: 1, Offset: 1}) + if err != nil { + t.Errorf("Memory Get error in test: %s", err) + } + rel, ok = value.GetRelocatable() + if !ok || rel.SegmentIndex != 2 || rel.Offset != 0 { + t.Errorf("Wrong value for address 1:0: %d", rel) + } +} + +func TestInitializeRunnerNoBuiltinsNoProofModeNonEmptyProgram(t *testing.T) { + // Create a Program with one fake instruction + program_data := make([]memory.MaybeRelocatable, 1) + program_data[0] = *memory.NewMaybeRelocatableInt(1) + program := vm.Program{Data: program_data} + // Create CairoRunner + runner := runners.NewCairoRunner(program) + // Initialize the runner + end_ptr, err := runner.Initialize() + if err != nil { + t.Errorf("Initialize error in test: %s", err) + } + if end_ptr.SegmentIndex != 3 || end_ptr.Offset != 0 { + t.Errorf("Wrong end ptr value, got %+v", end_ptr) + } + + // Check CairoRunner values + if runner.ProgramBase.SegmentIndex != 0 || runner.ProgramBase.Offset != 0 { + t.Errorf("Wrong ProgramBase value, got %+v", runner.ProgramBase) + } + + // Check Vm's RunContext values + if runner.Vm.RunContext.Pc.SegmentIndex != 0 || runner.Vm.RunContext.Pc.Offset != 0 { + t.Errorf("Wrong Pc value, got %+v", runner.Vm.RunContext.Pc) + } + if runner.Vm.RunContext.Ap.SegmentIndex != 1 || runner.Vm.RunContext.Ap.Offset != 2 { + t.Errorf("Wrong Ap value, got %+v", runner.Vm.RunContext.Ap) + } + if runner.Vm.RunContext.Fp.SegmentIndex != 1 || runner.Vm.RunContext.Fp.Offset != 2 { + t.Errorf("Wrong Fp value, got %+v", runner.Vm.RunContext.Fp) + } + + // Check memory + + // Program segment + // 0:0 program_data[0] + value, err := runner.Vm.Segments.Memory.Get(memory.Relocatable{SegmentIndex: 0, Offset: 0}) + if err != nil { + t.Errorf("Memory Get error in test: %s", err) + } + int, ok := value.GetInt() + if !ok || int.Felt != 1 { + t.Errorf("Wrong value for address 0:0: %d", int) + } + + // Execution segment + // 1:0 end_ptr + value, err = runner.Vm.Segments.Memory.Get(memory.Relocatable{SegmentIndex: 1, Offset: 0}) + if err != nil { + t.Errorf("Memory Get error in test: %s", err) + } + rel, ok := value.GetRelocatable() + if !ok || rel.SegmentIndex != 3 || rel.Offset != 0 { + t.Errorf("Wrong value for address 1:0: %d", rel) + } + // 1:1 return_fp + value, err = runner.Vm.Segments.Memory.Get(memory.Relocatable{SegmentIndex: 1, Offset: 1}) + if err != nil { + t.Errorf("Memory Get error in test: %s", err) + } + rel, ok = value.GetRelocatable() + if !ok || rel.SegmentIndex != 2 || rel.Offset != 0 { + t.Errorf("Wrong value for address 1:0: %d", rel) + } +} diff --git a/pkg/vm/memory/relocatable.go b/pkg/vm/memory/relocatable.go index ba727ec5..2e62ebd4 100644 --- a/pkg/vm/memory/relocatable.go +++ b/pkg/vm/memory/relocatable.go @@ -29,7 +29,7 @@ func (r *Relocatable) RelocateAddress(relocationTable *[]uint) uint { type Int struct { // FIXME: Here we should use Lambdaworks felt, just mocking // this for now. - felt uint + Felt uint } // MaybeRelocatable is the type of the memory cells in the Cairo @@ -69,7 +69,7 @@ func (m *MaybeRelocatable) GetRelocatable() (Relocatable, bool) { func (m *MaybeRelocatable) RelocateValue(relocationTable *[]uint) (uint, error) { inner_int, ok := m.GetInt() if ok { - return inner_int.felt, nil + return inner_int.Felt, nil } inner_relocatable, ok := m.GetRelocatable() diff --git a/pkg/vm/program.go b/pkg/vm/program.go index 287ed4e8..42ac90d6 100644 --- a/pkg/vm/program.go +++ b/pkg/vm/program.go @@ -1,5 +1,7 @@ package vm +import "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + type Program struct { - Data []uint + Data []memory.MaybeRelocatable } diff --git a/pkg/vm/vm_core.go b/pkg/vm/vm_core.go index 2f92d0c2..f0b41fbb 100644 --- a/pkg/vm/vm_core.go +++ b/pkg/vm/vm_core.go @@ -5,13 +5,11 @@ import "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" // VirtualMachine represents the Cairo VM. // Runs Cairo assembly and produces an execution trace. type VirtualMachine struct { - runContext RunContext + RunContext RunContext currentStep uint - Segments *memory.MemorySegmentManager + Segments memory.MemorySegmentManager } func NewVirtualMachine() *VirtualMachine { - segments := memory.NewMemorySegmentManager() - - return &VirtualMachine{Segments: segments} + return &VirtualMachine{Segments: *memory.NewMemorySegmentManager()} }