From bc156514c92752993a4cf7b3d8ec1e76666a68dc Mon Sep 17 00:00:00 2001 From: Malatrax Date: Wed, 7 Aug 2024 18:38:28 +0200 Subject: [PATCH 1/2] 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`. * From 2007545c339e6087d55e76af5e1a8d48e25b444e Mon Sep 17 00:00:00 2001 From: Malatrax Date: Wed, 7 Aug 2024 18:57:49 +0200 Subject: [PATCH 2/2] refactor: add a common method to get a SegmentValue and two methods to enforce Felt or Relocatable --- src/errors/virtualMachine.ts | 11 +--- src/vm/virtualMachine.test.ts | 68 ++++++++++--------------- src/vm/virtualMachine.ts | 96 +++++++++++------------------------ 3 files changed, 57 insertions(+), 118 deletions(-) diff --git a/src/errors/virtualMachine.ts b/src/errors/virtualMachine.ts index 82543214..4542882a 100644 --- a/src/errors/virtualMachine.ts +++ b/src/errors/virtualMachine.ts @@ -1,4 +1,4 @@ -import { OpType, ResOperand } from 'hints/hintParamsSchema'; +import { ResOperand } from 'hints/hintParamsSchema'; import { Relocatable } from 'primitives/relocatable'; import { SegmentValue } from 'primitives/segmentValue'; @@ -65,12 +65,3 @@ 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 1f7f7d6a..13b2a826 100644 --- a/src/vm/virtualMachine.test.ts +++ b/src/vm/virtualMachine.test.ts @@ -1,11 +1,7 @@ import { test, expect, describe, spyOn } from 'bun:test'; import { ExpectedFelt, ExpectedRelocatable } from 'errors/primitives'; -import { - CannotExtractRelocatable, - InvalidBufferResOp, - UnusedRes, -} from 'errors/virtualMachine'; +import { InvalidBufferResOp, UnusedRes } from 'errors/virtualMachine'; import { Felt } from 'primitives/felt'; import { Relocatable } from 'primitives/relocatable'; @@ -756,13 +752,6 @@ describe('VirtualMachine', () => { }, new Relocatable(2, 4), ], - // [ - // { - // type: OpType.Immediate, - // value: new Felt(5n), - // }, - // new Felt(5n), - // ], [ { type: OpType.BinOp, @@ -772,15 +761,6 @@ describe('VirtualMachine', () => { }, 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, @@ -793,18 +773,6 @@ describe('VirtualMachine', () => { }, 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) => { @@ -821,13 +789,27 @@ describe('VirtualMachine', () => { } ); + test('should throw ExpectedRelocatable when extracting from an Immediate 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); + + const resOperand = { + type: OpType.Immediate, + value: new Felt(5n), + }; + expect(() => vm.getResOperandRelocatable(resOperand)).toThrow( + new ExpectedRelocatable(resOperand.value) + ); + }); + test.each([ - [ - { - type: OpType.Immediate, - value: new Felt(5n), - }, - ], [ { type: OpType.BinOp, @@ -835,6 +817,7 @@ describe('VirtualMachine', () => { a: { register: Register.Fp, offset: 0 }, b: { type: OpType.Immediate, value: new Felt(5n) }, }, + new Relocatable(2, 4), ], [ { @@ -846,10 +829,11 @@ describe('VirtualMachine', () => { cell: { register: Register.Ap, offset: 2 }, }, }, + new Relocatable(2, 4), ], ])( - 'should throw CannotExtractRelocatable with Immediate BinOp + Operation.Mul ResOperand', - (resOperand: ResOperand) => { + 'should throw ExpectedFelt with BinOp + Operation.Mul ResOperand', + (resOperand: ResOperand, receivedValue: Relocatable) => { const vm = new VirtualMachine(); vm.memory.addSegment(); vm.memory.addSegment(); @@ -860,7 +844,7 @@ describe('VirtualMachine', () => { 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) + new ExpectedFelt(receivedValue) ); } ); diff --git a/src/vm/virtualMachine.ts b/src/vm/virtualMachine.ts index 54d5c6f4..0216925c 100644 --- a/src/vm/virtualMachine.ts +++ b/src/vm/virtualMachine.ts @@ -10,7 +10,6 @@ import { InvalidCallOp0Value, UndefinedOp1, InvalidBufferResOp, - CannotExtractRelocatable, } from 'errors/virtualMachine'; import { DictNotFound } from 'errors/dictionary'; import { InvalidCellRefRegister, UnknownHint } from 'errors/hints'; @@ -551,101 +550,65 @@ export class VirtualMachine { } /** - * Return the Felt value defined by `resOperand` + * Get the Felt defined by `resOperand` * - * Generic patterns: - * - Deref: `[register + offset]` - * - DoubleDeref: `[[register + offset1] + offset2]` - * - Immediate: `0x1000` - * - BinOp (Add): `[register1 + offset1] + [register2 + offset2]` - * or `[register1 + offset1] + immediate` - * - BinOp (Mul): `[register1 + offset1] * [register2 + offset2]` - * or `[register1 + offset1] * immediate` - * - * @param {ResOperand} resOperand - The ResOperand to extract a Felt from. + * @param 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 { - switch (resOperand.type) { - case OpType.Deref: - return this.getFelt((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 || !isFelt(value)) throw new ExpectedFelt(value); - return value; - - case OpType.Immediate: - return (resOperand as Immediate).value; - - case OpType.BinOp: - const binOp = resOperand as BinOp; - const a = this.getFelt(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); + const value = this.getResOperandSegmentValue(resOperand); + if (!isFelt(value)) throw new ExpectedFelt(value); + return value; + } - case Operation.Mul: - return a.mul(b); - } - } + /** + * Get the Relocatable defined by `resOperand` + * + * @param resOperand - The ResOperand to extract a Relocatable from. + * @returns {Relocatable} The value expressed by the given ResOperand. + */ + getResOperandRelocatable(resOperand: ResOperand): Relocatable { + const value = this.getResOperandSegmentValue(resOperand); + if (!isRelocatable(value)) throw new ExpectedRelocatable(value); + return value; } /** - * Return the Relocatable defined by `resOperand` + * Return the SegmentValue value defined by `resOperand` * * Generic patterns: * - Deref: `[register + offset]` * - DoubleDeref: `[[register + offset1] + offset2]` - * - Immediate: Forbidden operation on `Relocatable` + * - Immediate: `0x1000` * - BinOp (Add): `[register1 + offset1] + [register2 + offset2]` * or `[register1 + offset1] + immediate` - * - BinOp (Mul): Forbidden operation on `Relocatable` + * - BinOp (Mul): `[register1 + offset1] * [register2 + offset2]` + * or `[register1 + offset1] * immediate` * - * @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. + * @param {ResOperand} resOperand - The ResOperand to extract a Felt from. + * @returns {Felt} The value expressed by the given ResOperand. + * @throws {ExpectedFelt} If ResOperand is a BinOp (Mul) with `a` being a Relocatable. * * NOTE: used in Cairo hints */ - getResOperandRelocatable(resOperand: ResOperand): Relocatable { + getResOperandSegmentValue(resOperand: ResOperand): SegmentValue { switch (resOperand.type) { case OpType.Deref: - return this.getRelocatable((resOperand as Deref).cell); + return this.getSegmentValue((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); + if (!value) throw new UndefinedSegmentValue(); return value; case OpType.Immediate: - throw new CannotExtractRelocatable(resOperand.type); + return (resOperand as Immediate).value; case OpType.BinOp: const binOp = resOperand as BinOp; - const a = this.getRelocatable(binOp.a); + const a = this.getSegmentValue(binOp.a); let b: Felt | undefined = undefined; switch (binOp.b.type) { @@ -666,7 +629,8 @@ export class VirtualMachine { return a.add(b); case Operation.Mul: - throw new CannotExtractRelocatable(resOperand.type); + if (!isFelt(a)) throw new ExpectedFelt(a); + return a.mul(b); } } }