From bc156514c92752993a4cf7b3d8ec1e76666a68dc Mon Sep 17 00:00:00 2001 From: Malatrax Date: Wed, 7 Aug 2024 18:38:28 +0200 Subject: [PATCH] feat: add gestResOperandRelocatable method --- src/errors/virtualMachine.ts | 11 ++- src/vm/virtualMachine.test.ts | 133 +++++++++++++++++++++++++++++++++- src/vm/virtualMachine.ts | 67 ++++++++++++++++- 3 files changed, 207 insertions(+), 4 deletions(-) diff --git a/src/errors/virtualMachine.ts b/src/errors/virtualMachine.ts index 4542882a..82543214 100644 --- a/src/errors/virtualMachine.ts +++ b/src/errors/virtualMachine.ts @@ -1,4 +1,4 @@ -import { ResOperand } from 'hints/hintParamsSchema'; +import { OpType, ResOperand } from 'hints/hintParamsSchema'; import { Relocatable } from 'primitives/relocatable'; import { SegmentValue } from 'primitives/segmentValue'; @@ -65,3 +65,12 @@ export class InvalidBufferResOp extends VirtualMachineError { super(`Cannot extract buffer from the given ResOperand: ${resOperand}`); } } + +/** The given `resOperand` cannot be extracted as a Relocatable. */ +export class CannotExtractRelocatable extends VirtualMachineError { + constructor(type: OpType) { + super( + `The ResOperand ${type} cannot be extracted as a Relocatable. It doesn't support Immediate nor BinOp multiplicaton.` + ); + } +} diff --git a/src/vm/virtualMachine.test.ts b/src/vm/virtualMachine.test.ts index 3718179b..1f7f7d6a 100644 --- a/src/vm/virtualMachine.test.ts +++ b/src/vm/virtualMachine.test.ts @@ -1,7 +1,11 @@ import { test, expect, describe, spyOn } from 'bun:test'; import { ExpectedFelt, ExpectedRelocatable } from 'errors/primitives'; -import { InvalidBufferResOp, UnusedRes } from 'errors/virtualMachine'; +import { + CannotExtractRelocatable, + InvalidBufferResOp, + UnusedRes, +} from 'errors/virtualMachine'; import { Felt } from 'primitives/felt'; import { Relocatable } from 'primitives/relocatable'; @@ -721,7 +725,7 @@ describe('VirtualMachine', () => { new Felt(21n), ], ])( - 'should properly read ResOperand', + 'should properly read ResOperand as Felt', (resOperand: ResOperand, expected: Felt) => { const vm = new VirtualMachine(); vm.memory.addSegment(); @@ -735,6 +739,131 @@ describe('VirtualMachine', () => { expect(vm.getResOperandValue(resOperand)).toEqual(expected); } ); + + test.each([ + [ + { + type: OpType.Deref, + cell: { register: Register.Ap, offset: 0 }, + }, + new Relocatable(2, 4), + ], + [ + { + type: OpType.DoubleDeref, + cell: { register: Register.Ap, offset: 1 }, + offset: -1, + }, + new Relocatable(2, 4), + ], + // [ + // { + // type: OpType.Immediate, + // value: new Felt(5n), + // }, + // new Felt(5n), + // ], + [ + { + type: OpType.BinOp, + op: Operation.Add, + a: { register: Register.Fp, offset: 0 }, + b: { type: OpType.Immediate, value: new Felt(5n) }, + }, + new Relocatable(2, 9), + ], + // [ + // { + // type: OpType.BinOp, + // op: Operation.Mul, + // a: { register: Register.Fp, offset: 0 }, + // b: { type: OpType.Immediate, value: new Felt(5n) }, + // }, + // new Felt(15n), + // ], + [ + { + type: OpType.BinOp, + op: Operation.Add, + a: { register: Register.Ap, offset: 0 }, + b: { + type: OpType.Deref, + cell: { register: Register.Ap, offset: 2 }, + }, + }, + new Relocatable(2, 16), + ], + // [ + // { + // type: OpType.BinOp, + // op: Operation.Mul, + // a: { register: Register.Ap, offset: 0 }, + // b: { + // type: OpType.Deref, + // cell: { register: Register.Ap, offset: 1 }, + // }, + // }, + // new Felt(21n), + // ], + ])( + 'should properly read ResOperand as Relocatable', + (resOperand: ResOperand, expected: Relocatable) => { + const vm = new VirtualMachine(); + vm.memory.addSegment(); + vm.memory.addSegment(); + const address0 = new Relocatable(2, 4); + const address1 = new Relocatable(1, 1); + const value = new Felt(12n); + vm.memory.assertEq(vm.ap, address0); + vm.memory.assertEq(vm.ap.add(1), address1); + vm.memory.assertEq(vm.ap.add(2), value); + expect(vm.getResOperandRelocatable(resOperand)).toEqual(expected); + } + ); + + test.each([ + [ + { + type: OpType.Immediate, + value: new Felt(5n), + }, + ], + [ + { + type: OpType.BinOp, + op: Operation.Mul, + a: { register: Register.Fp, offset: 0 }, + b: { type: OpType.Immediate, value: new Felt(5n) }, + }, + ], + [ + { + type: OpType.BinOp, + op: Operation.Mul, + a: { register: Register.Ap, offset: 0 }, + b: { + type: OpType.Deref, + cell: { register: Register.Ap, offset: 2 }, + }, + }, + ], + ])( + 'should throw CannotExtractRelocatable with Immediate BinOp + Operation.Mul ResOperand', + (resOperand: ResOperand) => { + const vm = new VirtualMachine(); + vm.memory.addSegment(); + vm.memory.addSegment(); + const address0 = new Relocatable(2, 4); + const address1 = new Relocatable(1, 1); + const value = new Felt(12n); + vm.memory.assertEq(vm.ap, address0); + vm.memory.assertEq(vm.ap.add(1), address1); + vm.memory.assertEq(vm.ap.add(2), value); + expect(() => vm.getResOperandRelocatable(resOperand)).toThrow( + new CannotExtractRelocatable(resOperand.type) + ); + } + ); }); }); }); diff --git a/src/vm/virtualMachine.ts b/src/vm/virtualMachine.ts index f33ae157..54d5c6f4 100644 --- a/src/vm/virtualMachine.ts +++ b/src/vm/virtualMachine.ts @@ -10,6 +10,7 @@ import { InvalidCallOp0Value, UndefinedOp1, InvalidBufferResOp, + CannotExtractRelocatable, } from 'errors/virtualMachine'; import { DictNotFound } from 'errors/dictionary'; import { InvalidCellRefRegister, UnknownHint } from 'errors/hints'; @@ -550,7 +551,7 @@ export class VirtualMachine { } /** - * Return the value defined by `resOperand` + * Return the Felt value defined by `resOperand` * * Generic patterns: * - Deref: `[register + offset]` @@ -561,6 +562,9 @@ export class VirtualMachine { * - BinOp (Mul): `[register1 + offset1] * [register2 + offset2]` * or `[register1 + offset1] * immediate` * + * @param {ResOperand} resOperand - The ResOperand to extract a Felt from. + * @returns {Felt} The value expressed by the given ResOperand. + * * NOTE: used in Cairo hints */ getResOperandValue(resOperand: ResOperand): Felt { @@ -606,6 +610,67 @@ export class VirtualMachine { } } + /** + * Return the Relocatable defined by `resOperand` + * + * Generic patterns: + * - Deref: `[register + offset]` + * - DoubleDeref: `[[register + offset1] + offset2]` + * - Immediate: Forbidden operation on `Relocatable` + * - BinOp (Add): `[register1 + offset1] + [register2 + offset2]` + * or `[register1 + offset1] + immediate` + * - BinOp (Mul): Forbidden operation on `Relocatable` + * + * @param {ResOperand} resOperand - The ResOperand to extract a Relocatable from. + * @returns {Relocatable} The value expressed by the given ResOperand. + * @throws {CannotExtractRelocatable} if OpType is Immediate or BinOp with a Mul operation. + * + * NOTE: used in Cairo hints + */ + getResOperandRelocatable(resOperand: ResOperand): Relocatable { + switch (resOperand.type) { + case OpType.Deref: + return this.getRelocatable((resOperand as Deref).cell); + + case OpType.DoubleDeref: + const dDeref = resOperand as DoubleDeref; + const deref = this.getRelocatable(dDeref.cell); + const value = this.memory.get(deref.add(dDeref.offset)); + if (!value || !isRelocatable(value)) + throw new ExpectedRelocatable(value); + return value; + + case OpType.Immediate: + throw new CannotExtractRelocatable(resOperand.type); + + case OpType.BinOp: + const binOp = resOperand as BinOp; + const a = this.getRelocatable(binOp.a); + + let b: Felt | undefined = undefined; + switch (binOp.b.type) { + case OpType.Deref: + b = this.getFelt((binOp.b as Deref).cell); + break; + + case OpType.Immediate: + b = (binOp.b as Immediate).value; + break; + + default: + throw new ExpectedFelt(b); + } + + switch (binOp.op) { + case Operation.Add: + return a.add(b); + + case Operation.Mul: + throw new CannotExtractRelocatable(resOperand.type); + } + } + } + /** * Return the address defined at `resOperand`. *