From d27ad4c59f093d2b2eafc7a75ab0d036240c1bc9 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Thu, 23 Nov 2023 08:42:14 +0300 Subject: [PATCH 1/2] MIR: generalize `interpret` signature a bit Use slice reference instead of a vector reference --- contrib/mir/src/interpreter.rs | 107 ++++++++++++++++----------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index d7f23c4329ed..d83e78684a7f 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -89,7 +89,7 @@ impl<'a> Instruction<'a> { } fn interpret<'a>( - ast: &Vec>, + ast: &[Instruction<'a>], ctx: &mut Ctx, stack: &mut IStack<'a>, ) -> Result<(), InterpretError<'a>> { @@ -966,7 +966,7 @@ mod interpreter_tests { let mut stack = stk![]; assert_eq!( interpret( - &vec![Push(V::String("foo".to_owned()))], + &[Push(V::String("foo".to_owned()))], &mut Ctx::default(), &mut stack ), @@ -979,7 +979,7 @@ mod interpreter_tests { fn push_unit_value() { let mut stack = stk![]; assert_eq!( - interpret(&vec![Push(V::Unit)], &mut Ctx::default(), &mut stack), + interpret(&[Push(V::Unit)], &mut Ctx::default(), &mut stack), Ok(()) ); assert_eq!(stack, stk![V::Unit]); @@ -989,7 +989,7 @@ mod interpreter_tests { fn unit_instruction() { let mut stack = stk![]; let mut ctx = Ctx::default(); - assert!(interpret(&vec![Unit], &mut ctx, &mut stack).is_ok()); + assert!(interpret(&[Unit], &mut ctx, &mut stack).is_ok()); assert_eq!(stack, stk![V::Unit]); assert_eq!( ctx.gas.milligas(), @@ -1002,7 +1002,7 @@ mod interpreter_tests { let mut stack = stk![]; let mut ctx = Ctx::default(); assert!(interpret( - &vec![Push(V::new_pair( + &[Push(V::new_pair( V::int(-5), V::new_pair(V::nat(3), V::Bool(false)) ))], @@ -1028,7 +1028,7 @@ mod interpreter_tests { let mut stack = stk![]; let mut ctx = Ctx::default(); assert!(interpret( - &vec![Push(V::new_option(Some(V::int(-5))))], + &[Push(V::new_option(Some(V::int(-5))))], &mut ctx, &mut stack ) @@ -1045,7 +1045,7 @@ mod interpreter_tests { let mut stack = stk![]; let mut ctx = Ctx::default(); assert!(interpret( - &vec![ + &[ Push(V::new_pair( V::int(-5), V::new_pair(V::nat(3), V::Bool(false)) @@ -1071,7 +1071,7 @@ mod interpreter_tests { let mut stack = stk![]; let mut ctx = Ctx::default(); assert!(interpret( - &vec![ + &[ Push(V::new_pair( V::new_pair(V::nat(3), V::Bool(false)), V::int(-5), @@ -1095,28 +1095,28 @@ mod interpreter_tests { #[test] fn pair() { let mut stack = stk![V::nat(42), V::Bool(false)]; // NB: bool is top - assert!(interpret(&vec![Pair], &mut Ctx::default(), &mut stack).is_ok()); + assert!(interpret(&[Pair], &mut Ctx::default(), &mut stack).is_ok()); assert_eq!(stack, stk![V::new_pair(V::Bool(false), V::nat(42))]); } #[test] fn unpair() { let mut stack = stk![V::new_pair(V::Bool(false), V::nat(42))]; - assert!(interpret(&vec![Unpair], &mut Ctx::default(), &mut stack).is_ok()); + assert!(interpret(&[Unpair], &mut Ctx::default(), &mut stack).is_ok()); assert_eq!(stack, stk![V::nat(42), V::Bool(false)]); } #[test] fn pair_car() { let mut stack = stk![V::nat(42), V::Bool(false)]; // NB: bool is top - assert!(interpret(&vec![Pair, Car], &mut Ctx::default(), &mut stack).is_ok()); + assert!(interpret(&[Pair, Car], &mut Ctx::default(), &mut stack).is_ok()); assert_eq!(stack, stk![V::Bool(false)]); } #[test] fn pair_cdr() { let mut stack = stk![V::nat(42), V::Bool(false)]; // NB: bool is top - assert!(interpret(&vec![Pair, Cdr], &mut Ctx::default(), &mut stack).is_ok()); + assert!(interpret(&[Pair, Cdr], &mut Ctx::default(), &mut stack).is_ok()); assert_eq!(stack, stk![V::nat(42)]); } @@ -1218,7 +1218,7 @@ mod interpreter_tests { fn some() { let mut stack = stk![V::int(5)]; let mut ctx = Ctx::default(); - assert!(interpret(&vec![ISome], &mut ctx, &mut stack).is_ok()); + assert!(interpret(&[ISome], &mut ctx, &mut stack).is_ok()); assert_eq!(stack, stk![V::new_option(Some(V::int(5)))]); assert_eq!( ctx.gas.milligas(), @@ -1230,7 +1230,7 @@ mod interpreter_tests { fn none() { let mut stack = stk![]; let mut ctx = Ctx::default(); - assert!(interpret(&vec![Instruction::None], &mut ctx, &mut stack).is_ok()); + assert!(interpret(&[Instruction::None], &mut ctx, &mut stack).is_ok()); assert_eq!(stack, stk![V::new_option(None)]); assert_eq!( ctx.gas.milligas(), @@ -1246,7 +1246,7 @@ mod interpreter_tests { let expected_cost = interpret_cost::compare(&stack[0], &stack[1]).unwrap() + interpret_cost::INTERPRET_RET; let mut ctx = Ctx::default(); - assert!(interpret(&vec![Compare], &mut ctx, &mut stack).is_ok()); + assert!(interpret(&[Compare], &mut ctx, &mut stack).is_ok()); assert_eq!(stack, stk!$res); assert_eq!(ctx.gas.milligas(), Gas::default().milligas() - expected_cost); }; @@ -1274,7 +1274,7 @@ mod interpreter_tests { let mut stack = stk![]; let mut ctx = Ctx::default(); ctx.amount = 100500; - assert_eq!(interpret(&vec![Amount], &mut ctx, &mut stack), Ok(())); + assert_eq!(interpret(&[Amount], &mut ctx, &mut stack), Ok(())); assert_eq!(stack, stk![V::Mutez(100500)]); assert_eq!( ctx.gas.milligas(), @@ -1288,7 +1288,7 @@ mod interpreter_tests { let mut ctx = Ctx::default(); assert_eq!( interpret( - &vec![Push(V::List(vec![V::int(1), V::int(2), V::int(3),].into()))], + &[Push(V::List(vec![V::int(1), V::int(2), V::int(3),].into()))], &mut ctx, &mut stack ), @@ -1308,7 +1308,7 @@ mod interpreter_tests { fn nil() { let mut stack = stk![]; let mut ctx = Ctx::default(); - assert_eq!(interpret(&vec![Nil], &mut ctx, &mut stack), Ok(())); + assert_eq!(interpret(&[Nil], &mut ctx, &mut stack), Ok(())); assert_eq!(stack, stk![V::List(vec![].into())]); assert_eq!( ctx.gas.milligas(), @@ -1320,7 +1320,7 @@ mod interpreter_tests { fn cons() { let mut stack = stk![V::List(vec![V::int(321)].into()), V::int(123)]; let mut ctx = Ctx::default(); - assert_eq!(interpret(&vec![Cons], &mut ctx, &mut stack), Ok(())); + assert_eq!(interpret(&[Cons], &mut ctx, &mut stack), Ok(())); assert_eq!(stack, stk![V::List(vec![V::int(123), V::int(321)].into())]); assert_eq!( ctx.gas.milligas(), @@ -1337,7 +1337,7 @@ mod interpreter_tests { (V::int(2), V::String("bar".to_owned())), ]); assert_eq!( - interpret(&vec![Push(V::Map(map.clone()))], &mut ctx, &mut stack), + interpret(&[Push(V::Map(map.clone()))], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![V::Map(map)]); @@ -1356,7 +1356,7 @@ mod interpreter_tests { ]); let mut stack = stk![V::Map(map), V::int(1)]; assert_eq!( - interpret(&vec![Get(overloads::Get::Map)], &mut ctx, &mut stack), + interpret(&[Get(overloads::Get::Map)], &mut ctx, &mut stack), Ok(()) ); assert_eq!( @@ -1380,7 +1380,7 @@ mod interpreter_tests { ]); let mut stack = stk![V::Map(map), V::int(100500)]; assert_eq!( - interpret(&vec![Get(overloads::Get::Map)], &mut ctx, &mut stack), + interpret(&[Get(overloads::Get::Map)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![V::Option(None)]); @@ -1401,7 +1401,7 @@ mod interpreter_tests { ]); let mut stack = stk![TypedValue::Map(map), TypedValue::int(1)]; assert_eq!( - interpret(&vec![Mem(overloads::Mem::Map)], &mut ctx, &mut stack), + interpret(&[Mem(overloads::Mem::Map)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![TypedValue::Bool(true)]); @@ -1422,7 +1422,7 @@ mod interpreter_tests { ]); let mut stack = stk![TypedValue::Map(map), TypedValue::int(100500)]; assert_eq!( - interpret(&vec![Mem(overloads::Mem::Map)], &mut ctx, &mut stack), + interpret(&[Mem(overloads::Mem::Map)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![TypedValue::Bool(false)]); @@ -1434,7 +1434,7 @@ mod interpreter_tests { let set = BTreeSet::from([TypedValue::int(1), TypedValue::int(2)]); let mut stack = stk![TypedValue::Set(set), TypedValue::int(1)]; assert_eq!( - interpret(&vec![Mem(overloads::Mem::Set)], &mut ctx, &mut stack), + interpret(&[Mem(overloads::Mem::Set)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![TypedValue::Bool(true)]); @@ -1452,7 +1452,7 @@ mod interpreter_tests { let set = BTreeSet::from([TypedValue::int(1), TypedValue::int(2)]); let mut stack = stk![TypedValue::Set(set), TypedValue::int(100500)]; assert_eq!( - interpret(&vec![Mem(overloads::Mem::Set)], &mut ctx, &mut stack), + interpret(&[Mem(overloads::Mem::Set)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![TypedValue::Bool(false)]); @@ -1462,7 +1462,7 @@ mod interpreter_tests { fn empty_set() { let mut ctx = Ctx::default(); let mut stack = stk![]; - assert_eq!(interpret(&vec![EmptySet], &mut ctx, &mut stack), Ok(())); + assert_eq!(interpret(&[EmptySet], &mut ctx, &mut stack), Ok(())); assert_eq!(stack, stk![TypedValue::Set(BTreeSet::new())]); assert_eq!( ctx.gas.milligas(), @@ -1480,7 +1480,7 @@ mod interpreter_tests { TypedValue::int(1) ]; assert_eq!( - interpret(&vec![Update(overloads::Update::Set)], &mut ctx, &mut stack), + interpret(&[Update(overloads::Update::Set)], &mut ctx, &mut stack), Ok(()) ); assert_eq!( @@ -1505,7 +1505,7 @@ mod interpreter_tests { TypedValue::int(1) ]; assert_eq!( - interpret(&vec![Update(overloads::Update::Set)], &mut ctx, &mut stack), + interpret(&[Update(overloads::Update::Set)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![TypedValue::Set(BTreeSet::new())]); @@ -1527,7 +1527,7 @@ mod interpreter_tests { TypedValue::int(1) ]; assert_eq!( - interpret(&vec![Update(overloads::Update::Set)], &mut ctx, &mut stack), + interpret(&[Update(overloads::Update::Set)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![TypedValue::Set(set)]); @@ -1548,7 +1548,7 @@ mod interpreter_tests { TypedValue::int(1) ]; assert_eq!( - interpret(&vec![Update(overloads::Update::Set)], &mut ctx, &mut stack), + interpret(&[Update(overloads::Update::Set)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![TypedValue::Set(BTreeSet::new())]); @@ -1570,7 +1570,7 @@ mod interpreter_tests { V::int(1) ]; assert_eq!( - interpret(&vec![Update(overloads::Update::Map)], &mut ctx, &mut stack), + interpret(&[Update(overloads::Update::Map)], &mut ctx, &mut stack), Ok(()) ); assert_eq!( @@ -1598,7 +1598,7 @@ mod interpreter_tests { V::int(1) ]; assert_eq!( - interpret(&vec![Update(overloads::Update::Map)], &mut ctx, &mut stack), + interpret(&[Update(overloads::Update::Map)], &mut ctx, &mut stack), Ok(()) ); assert_eq!( @@ -1622,7 +1622,7 @@ mod interpreter_tests { let map = BTreeMap::from([(V::int(1), V::String("bar".to_owned()))]); let mut stack = stk![V::Map(map), V::new_option(None), V::int(1)]; assert_eq!( - interpret(&vec![Update(overloads::Update::Map)], &mut ctx, &mut stack), + interpret(&[Update(overloads::Update::Map)], &mut ctx, &mut stack), Ok(()) ); assert_eq!(stack, stk![V::Map(BTreeMap::new())]); @@ -1639,7 +1639,7 @@ mod interpreter_tests { let mut stack = stk![V::int(1), V::nat(2)]; assert_eq!( interpret( - &vec![ + &[ Seq(vec![Pair]), Seq(vec![Seq(vec![Car])]), Seq(vec![]), @@ -1658,7 +1658,7 @@ mod interpreter_tests { let mut stack = stk![V::nat(123), V::int(456)]; assert_eq!( interpret( - &vec![Add(overloads::Add::IntNat)], + &[Add(overloads::Add::IntNat)], &mut Ctx::default(), &mut stack ), @@ -1672,7 +1672,7 @@ mod interpreter_tests { let mut stack = stk![V::nat(123), V::int(-456)]; assert_eq!( interpret( - &vec![Add(overloads::Add::IntNat)], + &[Add(overloads::Add::IntNat)], &mut Ctx::default(), &mut stack ), @@ -1686,7 +1686,7 @@ mod interpreter_tests { let mut stack = stk![V::int(789), V::nat(42)]; assert_eq!( interpret( - &vec![Add(overloads::Add::NatInt)], + &[Add(overloads::Add::NatInt)], &mut Ctx::default(), &mut stack ), @@ -1700,7 +1700,7 @@ mod interpreter_tests { let mut stack = stk![V::int(-789), V::nat(42)]; assert_eq!( interpret( - &vec![Add(overloads::Add::NatInt)], + &[Add(overloads::Add::NatInt)], &mut Ctx::default(), &mut stack ), @@ -1716,7 +1716,7 @@ mod interpreter_tests { fn trigger_unreachable_state() { let mut stack = stk![]; interpret( - &vec![Add(overloads::Add::NatInt)], + &[Add(overloads::Add::NatInt)], &mut Ctx::default(), &mut stack, ) @@ -1730,7 +1730,7 @@ mod interpreter_tests { ctx.chain_id = chain_id.clone(); let start_milligas = ctx.gas.milligas(); let stk = &mut stk![]; - assert_eq!(interpret(&vec![Instruction::ChainId], ctx, stk), Ok(())); + assert_eq!(interpret(&[Instruction::ChainId], ctx, stk), Ok(())); assert_eq!(stk, &stk![V::ChainId(chain_id)]); assert_eq!( start_milligas - ctx.gas.milligas(), @@ -1741,7 +1741,7 @@ mod interpreter_tests { #[test] fn pack_instr() { let stack = &mut stk![TypedValue::new_pair(TypedValue::int(12), TypedValue::Unit)]; - assert_eq!(interpret(&vec![Pack], &mut Ctx::default(), stack), Ok(())); + assert_eq!(interpret(&[Pack], &mut Ctx::default(), stack), Ok(())); assert_eq!( stack, &stk![V::Bytes(hex::decode("050707000c030b").unwrap())] @@ -1753,10 +1753,7 @@ mod interpreter_tests { let stk = &mut stk![]; let ctx = &mut Ctx::default(); ctx.self_address = "KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT".try_into().unwrap(); - assert_eq!( - interpret(&vec![ISelf(Entrypoint::default())], ctx, stk), - Ok(()) - ); + assert_eq!(interpret(&[ISelf(Entrypoint::default())], ctx, stk), Ok(())); assert_eq!( stk, &stk![V::Contract( @@ -1781,7 +1778,7 @@ mod interpreter_tests { let ctx = &mut Ctx::default(); ctx.set_operation_counter(100); let start_milligas = ctx.gas.milligas(); - assert_eq!(interpret(&vec![TransferTokens], ctx, stk), Ok(())); + assert_eq!(interpret(&[TransferTokens], ctx, stk), Ok(())); assert_eq!( stk, &stk![V::new_operation(Operation::TransferTokens(tt), 101)] @@ -1802,7 +1799,7 @@ mod interpreter_tests { let ctx = &mut Ctx::default(); ctx.set_operation_counter(100); let start_milligas = ctx.gas.milligas(); - assert_eq!(interpret(&vec![I::SetDelegate], ctx, stk), Ok(())); + assert_eq!(interpret(&[I::SetDelegate], ctx, stk), Ok(())); assert_eq!( stk, &stk![V::new_operation(Operation::SetDelegate(sd), 101)] @@ -1829,7 +1826,7 @@ mod interpreter_tests { let ctx = &mut Ctx::default(); ctx.set_operation_counter(100); assert_eq!( - interpret(&vec![I::SetDelegate, I::Swap, I::SetDelegate], ctx, stk), + interpret(&[I::SetDelegate, I::Swap, I::SetDelegate], ctx, stk), Ok(()) ); assert_eq!( @@ -1847,7 +1844,7 @@ mod interpreter_tests { let ctx = &mut Ctx::default(); ctx.self_address = "KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT".try_into().unwrap(); assert_eq!( - interpret(&vec![ISelf(Entrypoint::try_from("foo").unwrap())], ctx, stk), + interpret(&[ISelf(Entrypoint::try_from("foo").unwrap())], ctx, stk), Ok(()) ); assert_eq!( @@ -1880,7 +1877,7 @@ mod interpreter_tests { let contract = V::Contract(address.clone()); let stk = &mut stk![contract]; let ctx = &mut Ctx::default(); - assert_eq!(interpret(&vec![Address], ctx, stk), Ok(())); + assert_eq!(interpret(&[Address], ctx, stk), Ok(())); assert_eq!(stk, &stk![V::Address(address)]); assert_eq!( ctx.gas.milligas(), @@ -1895,7 +1892,7 @@ mod interpreter_tests { let ctx = &mut Ctx::default(); let expected = expected.map(|str| V::String(str.to_string())); assert_eq!( - interpret(&vec![Slice(overloads::Slice::String)], ctx, stk), + interpret(&[Slice(overloads::Slice::String)], ctx, stk), Ok(()) ); assert_eq!(stk, &stk![V::new_option(expected)]); @@ -1917,7 +1914,7 @@ mod interpreter_tests { let ctx = &mut Ctx::default(); let expected = expected.map(|bytes| V::Bytes(bytes.to_vec())); assert_eq!( - interpret(&vec![Slice(overloads::Slice::Bytes)], ctx, stk), + interpret(&[Slice(overloads::Slice::Bytes)], ctx, stk), Ok(()) ); assert_eq!(stk, &stk![V::new_option(expected)]); @@ -1936,7 +1933,7 @@ mod interpreter_tests { fn left() { let mut stack = stk![V::nat(10)]; let mut ctx = Ctx::default(); - assert!(interpret(&vec![Instruction::Left], &mut ctx, &mut stack).is_ok()); + assert!(interpret(&[Instruction::Left], &mut ctx, &mut stack).is_ok()); assert_eq!(stack, stk![V::new_or(Or::Left(V::nat(10)))]); assert_eq!( ctx.gas.milligas(), @@ -1948,7 +1945,7 @@ mod interpreter_tests { fn right() { let mut stack = stk![V::nat(10)]; let mut ctx = Ctx::default(); - assert!(interpret(&vec![Instruction::Right], &mut ctx, &mut stack).is_ok()); + assert!(interpret(&[Instruction::Right], &mut ctx, &mut stack).is_ok()); assert_eq!(stack, stk![V::new_or(Or::Right(V::nat(10)))]); assert_eq!( ctx.gas.milligas(), -- GitLab From 32c0627c7bdbf6300d4185a89af11fc181395cb2 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Wed, 22 Nov 2023 18:41:21 +0300 Subject: [PATCH 2/2] MIR: Add EXEC instruction --- contrib/mir/src/ast.rs | 1 + contrib/mir/src/ast/michelson_lambda.rs | 17 ++++- contrib/mir/src/gas.rs | 1 + contrib/mir/src/interpreter.rs | 99 +++++++++++++++++++++++++ contrib/mir/src/typechecker.rs | 30 +++++--- 5 files changed, 137 insertions(+), 11 deletions(-) diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 60540d6ed83c..1f26c08daa36 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -309,6 +309,7 @@ pub enum Instruction<'a> { Left, Right, Lambda(Lambda<'a>), + Exec, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/contrib/mir/src/ast/michelson_lambda.rs b/contrib/mir/src/ast/michelson_lambda.rs index b335fbcf8794..be3c201eed8e 100644 --- a/contrib/mir/src/ast/michelson_lambda.rs +++ b/contrib/mir/src/ast/michelson_lambda.rs @@ -5,16 +5,29 @@ /* */ /******************************************************************************/ +use std::rc::Rc; + use super::{Instruction, Micheline}; #[derive(Debug, Clone, Eq, PartialEq)] pub enum Lambda<'a> { Lambda { micheline_code: Micheline<'a>, - code: Vec>, + code: Rc<[Instruction<'a>]>, // see Note: Rc in lambdas }, LambdaRec { micheline_code: Micheline<'a>, - code: Vec>, + code: Rc<[Instruction<'a>]>, // see Note: Rc in lambdas }, } + +/* *** Note: Rc in lambdas *** + +When interpreting recursive lambdas, the lambda itself is used as the argument +of its body, which means we'd need two copies of the body if we used `Vec`. To +avoid potentially costly cloning, we use an `Rc` instead. + +For non-recursive Lambda the same approach is used to make `LAMBDA` instruction +independent of the length of the code (there's a Lambda::clone call in the +implementation) +*/ diff --git a/contrib/mir/src/gas.rs b/contrib/mir/src/gas.rs index aacd34c5d238..d37346de813a 100644 --- a/contrib/mir/src/gas.rs +++ b/contrib/mir/src/gas.rs @@ -240,6 +240,7 @@ pub mod interpret_cost { pub const TRANSFER_TOKENS: u32 = 60; pub const SET_DELEGATE: u32 = 60; pub const LAMBDA: u32 = 10; + pub const EXEC: u32 = 10; pub const INTERPRET_RET: u32 = 15; // corresponds to KNil in the Tezos protocol pub const LOOP_ENTER: u32 = 10; // corresponds to KLoop_in in the Tezos protocol diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index d83e78684a7f..b8ae3581cf0e 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -7,6 +7,7 @@ use num_bigint::{BigInt, BigUint}; use num_traits::{Signed, Zero}; +use std::rc::Rc; use typed_arena::Arena; use crate::ast::*; @@ -546,6 +547,27 @@ fn interpret_one<'a>( ctx.gas.consume(interpret_cost::LAMBDA)?; stack.push(V::Lambda(lam.clone())); } + I::Exec => { + ctx.gas.consume(interpret_cost::EXEC)?; + let arg = pop!(); + let lam = pop!(V::Lambda); + let mut res_stk = match &lam { + Lambda::LambdaRec { code, .. } => { + // NB: this `clone` is constant-time as `code` is Rc + // See Note: Rc in lambdas + let code = Rc::clone(code); + let mut stk = stk![V::Lambda(lam), arg]; + interpret(&code, ctx, &mut stk)?; + stk + } + Lambda::Lambda { code, .. } => { + let mut stk = stk![arg]; + interpret(code, ctx, &mut stk)?; + stk + } + }; + stack.push(res_stk.pop().unwrap_or_else(|| unreachable_state())); + } I::Seq(nested) => interpret(nested, ctx, stack)?, } Ok(()) @@ -555,6 +577,7 @@ fn interpret_one<'a>( mod interpreter_tests { use std::collections::{BTreeMap, BTreeSet}; + use super::Lambda; use super::*; use crate::ast::michelson_address as addr; use crate::gas::Gas; @@ -1952,4 +1975,80 @@ mod interpreter_tests { Gas::default().milligas() - interpret_cost::RIGHT - interpret_cost::INTERPRET_RET ); } + + #[test] + fn exec() { + let mut stack = stk![ + TypedValue::Lambda(Lambda::Lambda { + micheline_code: Micheline::Seq(&[]), // ignored by the interpreter + code: vec![Unpair, Add(overloads::Add::IntNat)].into(), + }), + TypedValue::new_pair(TypedValue::int(1), TypedValue::nat(5)) + ]; + assert_eq!( + interpret_one(&Exec, &mut Ctx::default(), &mut stack), + Ok(()) + ); + assert_eq!(stack, stk![TypedValue::int(6)]); + } + + #[test] + fn exec_rec_1() { + let lam = Lambda::LambdaRec { + micheline_code: Micheline::Seq(&[]), + code: vec![ + Unpair, + If( + vec![ + Dup(None), + Add(overloads::Add::NatNat), + Push(TypedValue::Bool(false)), + Pair, + Exec, + ], + vec![Swap, Drop(None)], + ), + ] + .into(), + }; + let mut stack = stk![ + TypedValue::Lambda(lam.clone()), + TypedValue::new_pair(TypedValue::Bool(true), TypedValue::nat(5)) + ]; + assert_eq!( + interpret_one(&Exec, &mut Ctx::default(), &mut stack), + Ok(()) + ); + assert_eq!(stack, stk![TypedValue::nat(10)]); + } + + #[test] + fn exec_rec_2() { + let lam = Lambda::LambdaRec { + micheline_code: Micheline::Seq(&[]), + code: vec![ + Unpair, + If( + vec![ + Dup(None), + Add(overloads::Add::NatNat), + Push(TypedValue::Bool(false)), + Pair, + Exec, + ], + vec![Swap, Drop(None)], + ), + ] + .into(), + }; + let mut stack = stk![ + TypedValue::Lambda(lam.clone()), + TypedValue::new_pair(TypedValue::Bool(false), TypedValue::nat(5)) + ]; + assert_eq!( + interpret_one(&Exec, &mut Ctx::default(), &mut stack), + Ok(()) + ); + assert_eq!(stack, stk![TypedValue::nat(5)]); + } } diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index b10090f77481..8d2986cf1d0a 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -9,6 +9,7 @@ use num_bigint::{BigInt, BigUint, TryFromBigIntError}; use num_traits::{Signed, Zero}; use std::collections::hash_map::Entry; use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::rc::Rc; use tezos_crypto_rs::{base58::FromBase58CheckError, hash::FromBytesError}; pub mod type_props; @@ -1124,6 +1125,17 @@ pub(crate) fn typecheck_instruction<'a>( } (App(LAMBDA | LAMBDA_REC, expect_args!(3 last_seq), _), _) => unexpected_micheline!(), + (App(EXEC, [], _), [.., T::Lambda(_), _]) => { + let ty = pop!(); + let (in_ty, out_ty) = *pop!(T::Lambda); + ensure_ty_eq(ctx, &in_ty, &ty)?; + stack.push(out_ty); + I::Exec + } + (App(EXEC, [], _), [.., _, _]) => no_overload!(EXEC), + (App(EXEC, [], _), [] | [_]) => no_overload!(EXEC, len 2), + (App(EXEC, expect_args!(0), _), _) => unexpected_micheline!(), + (App(other, ..), _) => todo!("Unhandled instruction {other}"), (Seq(nested), _) => I::Seq(typecheck(nested, ctx, self_entrypoints, opt_stack)?), @@ -1333,7 +1345,7 @@ fn typecheck_lambda<'a>( } else { tc_stk![in_ty] }; - let code = typecheck(instrs, ctx, None, stk)?; + let code = Rc::from(typecheck(instrs, ctx, None, stk)?); unify_stacks(ctx, stk, tc_stk![out_ty])?; let micheline_code = Micheline::Seq(instrs); Ok(if recursive { @@ -4185,7 +4197,7 @@ mod typecheck_tests { .typecheck_instruction(&mut Ctx::default(), None, &[]), Ok(Push(TypedValue::Lambda(Lambda::Lambda { micheline_code: seq! { app!(DROP); app!(UNIT) }, - code: vec![Drop(None), Unit] + code: vec![Drop(None), Unit].into() }))) ); assert_eq!( @@ -4194,7 +4206,7 @@ mod typecheck_tests { .typecheck_instruction(&mut Ctx::default(), None, &[]), Ok(Lambda(Lambda::Lambda { micheline_code: seq! { app!(DROP); app!(UNIT) }, - code: vec![Drop(None), Unit] + code: vec![Drop(None), Unit].into() })) ); } @@ -4282,21 +4294,21 @@ mod typecheck_tests { #[test] fn push_lambda_rec() { assert_eq!( - parse("PUSH (lambda unit unit) (Lambda_rec { DIP { DROP } })") + parse("PUSH (lambda unit unit) (Lambda_rec { SWAP ; DROP })") .unwrap() .typecheck_instruction(&mut Ctx::default(), None, &[]), Ok(Push(TypedValue::Lambda(Lambda::LambdaRec { - micheline_code: seq! { app!(DIP[seq! { app!(DROP) } ]) }, - code: vec![Dip(None, vec![Drop(None)])] + micheline_code: seq! { app!(SWAP) ; app!(DROP) }, + code: vec![Swap, Drop(None)].into() }))) ); assert_eq!( - parse("LAMBDA_REC unit unit { DIP { DROP } }") + parse("LAMBDA_REC unit unit { SWAP ; DROP }") .unwrap() .typecheck_instruction(&mut Ctx::default(), None, &[]), Ok(Lambda(Lambda::LambdaRec { - micheline_code: seq! { app!(DIP[seq! { app!(DROP) } ]) }, - code: vec![Dip(None, vec![Drop(None)])] + micheline_code: seq! { app!(SWAP) ; app!(DROP) }, + code: vec![Swap, Drop(None)].into() })) ); } -- GitLab