From ff95f3118a34e1ab4fcd9fdd7cf9a3d6d9bb3cbf Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sun, 24 Nov 2024 17:30:05 +0100 Subject: [PATCH] Add function type --- crates/interpreter/Cargo.toml | 2 +- crates/interpreter/src/lib.rs | 22 +++-- crates/ir/src/builder/module_builder.rs | 4 + crates/ir/src/function.rs | 7 ++ crates/ir/src/interpret/data.rs | 15 ++- crates/ir/src/isa/evm.rs | 33 ++++--- crates/ir/src/isa/mod.rs | 18 +++- crates/ir/src/module.rs | 20 +++- crates/ir/src/types.rs | 33 ++++++- crates/parser/src/ast.rs | 10 ++ crates/parser/src/lib.rs | 7 ++ crates/parser/src/sonatina.pest | 14 +-- .../syntax/module/func_type.ast.snap | 95 +++++++++++++++++++ .../syntax/module/func_type.ir.snap | 10 ++ .../test_files/syntax/module/func_type.snap | 46 +++++++++ .../test_files/syntax/module/func_type.sntn | 6 ++ 16 files changed, 304 insertions(+), 38 deletions(-) create mode 100644 crates/parser/test_files/syntax/module/func_type.ast.snap create mode 100644 crates/parser/test_files/syntax/module/func_type.ir.snap create mode 100644 crates/parser/test_files/syntax/module/func_type.snap create mode 100644 crates/parser/test_files/syntax/module/func_type.sntn diff --git a/crates/interpreter/Cargo.toml b/crates/interpreter/Cargo.toml index 99ee2102..55d0bcd7 100644 --- a/crates/interpreter/Cargo.toml +++ b/crates/interpreter/Cargo.toml @@ -22,6 +22,6 @@ dyn-clone = "1.0" [dev-dependencies] sonatina-parser = { path = "../parser" } -dir-test = "0.3" +dir-test = "0.4" regex = "1.11" once_cell = "1.20" diff --git a/crates/interpreter/src/lib.rs b/crates/interpreter/src/lib.rs index 8c6c6161..a4475463 100644 --- a/crates/interpreter/src/lib.rs +++ b/crates/interpreter/src/lib.rs @@ -169,7 +169,7 @@ impl State for Machine { }; let addr = addr.as_usize(); - let size = self.module_ctx.size_of(ty); + let size = self.module_ctx.size_of_unchecked(ty); if addr + size > self.memory.len() { return EvalValue::Undef; } @@ -181,7 +181,7 @@ impl State for Machine { match ty.resolve_compound(&self.module_ctx).unwrap() { CompoundTypeData::Array { elem: elem_ty, len } => { let mut addr = addr; - let elem_size = self.module_ctx.size_of(elem_ty); + let elem_size = self.module_ctx.size_of_unchecked(elem_ty); for _ in 0..len { let elem_addr = EvalValue::Imm(Immediate::I256(I256::from(addr))); let elem = self.load(elem_addr, elem_ty); @@ -196,13 +196,17 @@ impl State for Machine { let elem_addr = EvalValue::Imm(Immediate::I256(I256::from(addr))); let field = self.load(elem_addr, field_ty); fields.push(field); - addr += self.module_ctx.size_of(field_ty); + addr += self.module_ctx.size_of_unchecked(field_ty); } } CompoundTypeData::Ptr(_) => { unreachable!() } + + CompoundTypeData::Func { .. } => { + panic!("function type can't be placed in memory"); + } } return EvalValue::Aggregate { fields, ty }; @@ -228,7 +232,7 @@ impl State for Machine { panic!("udnef address in store") }; let addr = addr.as_usize(); - let size = self.module_ctx.size_of(ty); + let size = self.module_ctx.size_of_unchecked(ty); if addr + size > self.memory.len() { self.memory.resize(addr + size, 0); } @@ -241,7 +245,7 @@ impl State for Machine { match ty.resolve_compound(&self.module_ctx).unwrap() { CompoundTypeData::Array { elem: elem_ty, .. } => { let mut addr = addr; - let elem_size = self.module_ctx.size_of(elem_ty); + let elem_size = self.module_ctx.size_of_unchecked(elem_ty); for field in &fields { let elem_addr = EvalValue::Imm(Immediate::I256(I256::from(addr))); self.store(field.clone(), elem_addr, elem_ty); @@ -254,13 +258,17 @@ impl State for Machine { for (i, field_ty) in s.fields.into_iter().enumerate() { let elem_addr = EvalValue::Imm(Immediate::I256(I256::from(addr))); self.store(fields[i].clone(), elem_addr, field_ty); - addr += self.module_ctx.size_of(field_ty); + addr += self.module_ctx.size_of_unchecked(field_ty); } } CompoundTypeData::Ptr(_) => { unreachable!() } + + CompoundTypeData::Func { .. } => { + panic!("Function can't be stored in memory"); + } } return EvalValue::Undef; @@ -291,7 +299,7 @@ impl State for Machine { fn alloca(&mut self, ty: Type) -> EvalValue { let ptr = self.free_region; - self.free_region += self.module_ctx.size_of(ty); + self.free_region += self.module_ctx.size_of_unchecked(ty); EvalValue::Imm(Immediate::I256(I256::from(ptr))) } diff --git a/crates/ir/src/builder/module_builder.rs b/crates/ir/src/builder/module_builder.rs index 97a3f28c..28f1d29b 100644 --- a/crates/ir/src/builder/module_builder.rs +++ b/crates/ir/src/builder/module_builder.rs @@ -73,6 +73,10 @@ impl ModuleBuilder { self.ctx.with_ty_store_mut(|s| s.make_array(elem, len)) } + pub fn declare_func_type(&self, args: &[Type], ret_ty: Type) -> Type { + self.ctx.with_ty_store_mut(|s| s.make_func(args, ret_ty)) + } + pub fn ptr_type(&self, ty: Type) -> Type { self.ctx.with_ty_store_mut(|s| s.make_ptr(ty)) } diff --git a/crates/ir/src/function.rs b/crates/ir/src/function.rs index a433a2d1..b8ef1bf9 100644 --- a/crates/ir/src/function.rs +++ b/crates/ir/src/function.rs @@ -84,6 +84,13 @@ impl Signature { self.ret_ty } + pub fn func_ptr_type(&self, ctx: &ModuleCtx) -> Type { + ctx.with_ty_store_mut(|s| { + let func_ty = s.make_func(&self.args, self.ret_ty); + s.make_ptr(func_ty) + }) + } + #[doc(hidden)] pub fn set_ret_ty(&mut self, ty: Type) { self.ret_ty = ty; diff --git a/crates/ir/src/interpret/data.rs b/crates/ir/src/interpret/data.rs index 9d43c621..c1b6a06a 100644 --- a/crates/ir/src/interpret/data.rs +++ b/crates/ir/src/interpret/data.rs @@ -63,13 +63,13 @@ impl Interpret for Gep { match cmpd_data { CompoundTypeData::Array { elem, .. } => { - let elem_size = state.dfg().ctx.size_of(elem); + let elem_size = state.dfg().ctx.size_of_unchecked(elem); offset += elem_size * idx_value; current_ty = elem; } CompoundTypeData::Ptr(ty) => { - let size = state.dfg().ctx.size_of(ty); + let size = state.dfg().ctx.size_of_unchecked(ty); offset += size * idx_value; current_ty = ty; } @@ -78,13 +78,19 @@ impl Interpret for Gep { let mut local_offset = 0; for i in 0..idx_value { let field_ty = s.fields[i]; - let size = state.dfg().ctx.size_of(field_ty); - let align = state.dfg().ctx.align_of(field_ty); + let size = state.dfg().ctx.size_of_unchecked(field_ty); + let align = state.dfg().ctx.align_of_unchecked(field_ty); local_offset += align_to(offset + size, align); } offset += local_offset; current_ty = s.fields[idx_value]; } + + CompoundTypeData::Func { .. } => { + panic!( + "Invalid GEP: indexing into a function type with more indices remaining" + ); + } } } @@ -115,6 +121,7 @@ impl Interpret for InsertValue { CompoundTypeData::Array { len, .. } => len, CompoundTypeData::Struct(s) => s.fields.len(), CompoundTypeData::Ptr(_) => unreachable!(), + CompoundTypeData::Func { .. } => unreachable!(), }; vec![EvalValue::Undef; len] } diff --git a/crates/ir/src/isa/evm.rs b/crates/ir/src/isa/evm.rs index 6c42a95a..6d234267 100644 --- a/crates/ir/src/isa/evm.rs +++ b/crates/ir/src/isa/evm.rs @@ -2,7 +2,7 @@ use std::sync::LazyLock; use sonatina_triple::{Architecture, TargetTriple}; -use super::{Endian, Isa, TypeLayout}; +use super::{Endian, Isa, TypeLayout, TypeLayoutError}; use crate::{inst::evm::inst_set::EvmInstSet, module::ModuleCtx, types::CompoundTypeData, Type}; #[derive(Debug, Clone, Copy)] @@ -37,8 +37,8 @@ impl Isa for Evm { struct EvmTypeLayout {} impl TypeLayout for EvmTypeLayout { - fn size_of(&self, ty: crate::Type, ctx: &ModuleCtx) -> usize { - match ty { + fn size_of(&self, ty: crate::Type, ctx: &ModuleCtx) -> Result { + let size = match ty { Type::Unit => 0, Type::I1 => 1, Type::I8 => 1, @@ -51,10 +51,7 @@ impl TypeLayout for EvmTypeLayout { Type::Compound(cmpd) => { let cmpd_data = ctx.with_ty_store(|s| s.resolve_compound(cmpd).clone()); match cmpd_data { - CompoundTypeData::Array { elem, len } => { - // TODO: alignment! - self.size_of(elem, ctx) * len - } + CompoundTypeData::Array { elem, len } => self.size_of(elem, ctx)? * len, CompoundTypeData::Ptr(_) => 32, @@ -62,22 +59,30 @@ impl TypeLayout for EvmTypeLayout { if s.packed { panic!("packed data is not supported yet!"); } - s.fields - .iter() - .copied() - .fold(0, |acc, ty| acc + self.size_of(ty, ctx)) + let mut size = 0; + for &field in &s.fields { + size += self.size_of(field, ctx)?; + } + + size + } + + CompoundTypeData::Func { .. } => { + return Err(TypeLayoutError::UnrepresentableType(ty)) } } } - } + }; + + Ok(size) } fn pointer_repl(&self) -> Type { Type::I256 } - fn align_of(&self, _ty: Type, _ctx: &ModuleCtx) -> usize { - 1 + fn align_of(&self, _ty: Type, _ctx: &ModuleCtx) -> Result { + Ok(1) } fn endian(&self) -> Endian { diff --git a/crates/ir/src/isa/mod.rs b/crates/ir/src/isa/mod.rs index 124b7e54..843f2b07 100644 --- a/crates/ir/src/isa/mod.rs +++ b/crates/ir/src/isa/mod.rs @@ -13,12 +13,26 @@ pub trait Isa { } pub trait TypeLayout: Send + Sync { - fn size_of(&self, ty: Type, ctx: &ModuleCtx) -> usize; - fn align_of(&self, ty: Type, ctx: &ModuleCtx) -> usize; + fn size_of(&self, ty: Type, ctx: &ModuleCtx) -> Result; + fn align_of(&self, ty: Type, ctx: &ModuleCtx) -> Result; fn pointer_repl(&self) -> Type; fn endian(&self) -> Endian; } +#[derive(Debug, Clone)] +pub enum TypeLayoutError { + /// The type is unsupported by the ISA. + UnsupportedType(Type), + + /// An error indicating that the given type cannot be represented in memory. + /// + /// This error occurs when attempting to compute the size or alignment of a + /// type that is abstract or has no concrete representation in memory. + /// For example, function types or unresolved abstract types cannot be + /// laid out in memory. + UnrepresentableType(Type), +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Endian { Be, diff --git a/crates/ir/src/module.rs b/crates/ir/src/module.rs index 2147cdb4..832d0c59 100644 --- a/crates/ir/src/module.rs +++ b/crates/ir/src/module.rs @@ -7,7 +7,7 @@ use sonatina_triple::TargetTriple; use crate::{ global_variable::GlobalVariableStore, - isa::{Endian, Isa, TypeLayout}, + isa::{Endian, Isa, TypeLayout, TypeLayoutError}, types::TypeStore, Function, InstSetBase, Signature, Type, }; @@ -122,14 +122,22 @@ impl ModuleCtx { } } - pub fn size_of(&self, ty: Type) -> usize { + pub fn size_of(&self, ty: Type) -> Result { self.type_layout.size_of(ty, self) } - pub fn align_of(&self, ty: Type) -> usize { + pub fn align_of(&self, ty: Type) -> Result { self.type_layout.align_of(ty, self) } + pub fn size_of_unchecked(&self, ty: Type) -> usize { + self.size_of(ty).unwrap() + } + + pub fn align_of_unchecked(&self, ty: Type) -> usize { + self.align_of(ty).unwrap() + } + pub fn func_sig(&self, func_ref: FuncRef, f: F) -> R where F: FnOnce(&Signature) -> R, @@ -175,3 +183,9 @@ impl ModuleCtx { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FuncRef(u32); entity_impl!(FuncRef); + +impl FuncRef { + pub fn as_ptr_ty(self, ctx: &ModuleCtx) -> Type { + ctx.func_sig(self, |sig| sig.func_ptr_type(ctx)) + } +} diff --git a/crates/ir/src/types.rs b/crates/ir/src/types.rs index 24a4d243..b0095575 100644 --- a/crates/ir/src/types.rs +++ b/crates/ir/src/types.rs @@ -4,6 +4,7 @@ use std::{cmp, io}; use cranelift_entity::PrimaryMap; use indexmap::IndexMap; use rustc_hash::FxHashMap; +use smallvec::SmallVec; use crate::{ir_writer::WriteWithModule, module::ModuleCtx}; @@ -40,6 +41,14 @@ impl TypeStore { Type::Compound(compound) } + pub fn make_func(&mut self, args: &[Type], ret_ty: Type) -> Type { + let compound = self.make_compound(CompoundTypeData::Func { + args: args.into(), + ret_ty, + }); + Type::Compound(compound) + } + /// Returns `[StructDef]` if the given type is a struct type. pub fn struct_def(&self, ty: Type) -> Option<&StructData> { match ty { @@ -230,15 +239,37 @@ impl WriteWithModule for CompoundType { write!(w, "@{name}") } } + + CompoundTypeData::Func { args, ret_ty: ret } => { + write!(w, "(")?; + let mut args = args.iter(); + if let Some(arg_ty) = args.next() { + arg_ty.write(ctx, w)?; + }; + for arg_ty in args { + write!(w, ", ")?; + arg_ty.write(ctx, w)?; + } + + write!(w, ") -> ")?; + ret.write(ctx, w) + } }) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CompoundTypeData { - Array { elem: Type, len: usize }, + Array { + elem: Type, + len: usize, + }, Ptr(Type), Struct(StructData), + Func { + args: SmallVec<[Type; 8]>, + ret_ty: Type, + }, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index 064b7e2d..728b8e7e 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -511,6 +511,15 @@ impl FromSyntax for Type { } Rule::unit_type => TypeKind::Unit, Rule::struct_identifier => TypeKind::Struct(node.parse_str(Rule::struct_name)), + Rule::function_type => { + let args = node.descend_into(Rule::function_type_arg, |n| n.multi(Rule::type_name)); + let ret_ty = + node.descend_into(Rule::function_ret_type, |n| n.single(Rule::type_name)); + TypeKind::Func { + args, + ret_ty: Box::new(ret_ty), + } + } _ => unreachable!(), }; Type { @@ -526,6 +535,7 @@ pub enum TypeKind { Ptr(Box), Array(Box, usize), Struct(SmolStr), + Func { args: Vec, ret_ty: Box }, Unit, Error, } diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index c1177cdc..a41bc9c1 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -288,6 +288,13 @@ impl BuildCtx { .push(Error::Undefined(UndefinedKind::Type(name.clone()), t.span)); ir::Type::Unit }), + + ast::TypeKind::Func { args, ret_ty } => { + let args: Vec<_> = args.iter().map(|t| self.type_(mb, t)).collect(); + let ret_ty = self.type_(mb, ret_ty); + mb.declare_func_type(&args, ret_ty) + } + ast::TypeKind::Error => unreachable!(), } } diff --git a/crates/parser/src/sonatina.pest b/crates/parser/src/sonatina.pest index 388b79b7..0802f35d 100644 --- a/crates/parser/src/sonatina.pest +++ b/crates/parser/src/sonatina.pest @@ -36,12 +36,14 @@ block_ident = ${ "block" ~ block_number } block_number = { ASCII_DIGIT+ } value_name = ${ "v" ~ ASCII_DIGIT+ } -type_name = { primitive_type | ptr_type | array_type | unit_type | struct_identifier } -primitive_type = { "i8" | "i16" | "i32" | "i64" | "i128" | "i256" | "i1" } -ptr_type = ${ "*" ~ type_name } -array_type = !{ "[" ~ type_name ~ ";" ~ array_size ~ "]" } -array_size = { ASCII_DIGIT+ } -unit_type = { "unit" } +type_name = { primitive_type | ptr_type | array_type | unit_type | struct_identifier | function_type } +primitive_type = { "i8" | "i16" | "i32" | "i64" | "i128" | "i256" | "i1" } +ptr_type = ${ "*" ~ type_name } +array_type = !{ "[" ~ type_name ~ ";" ~ array_size ~ "]" } +array_size = { ASCII_DIGIT+ } +function_type = !{ function_type_arg ~ function_ret_type } +function_type_arg = { "(" ~ type_list ~ ")" } +unit_type = { "unit" } value_declaration = ${ value_name ~ "." ~ type_name } diff --git a/crates/parser/test_files/syntax/module/func_type.ast.snap b/crates/parser/test_files/syntax/module/func_type.ast.snap new file mode 100644 index 00000000..84a23f97 --- /dev/null +++ b/crates/parser/test_files/syntax/module/func_type.ast.snap @@ -0,0 +1,95 @@ +--- +source: crates/parser/tests/syntax.rs +expression: "Module {\n target: Some(\n TargetTriple {\n architecture: Evm,\n vendor: Ethereum,\n operating_system: Evm(\n London,\n ),\n },\n ),\n declared_functions: [],\n struct_types: [],\n functions: [\n Func {\n signature: FuncSignature {\n linkage: Public,\n name: FunctionName {\n name: \"higher_order\",\n ..\n },\n params: [\n ValueDeclaration(\n ValueName {\n string: \"v0\",\n ..\n },\n Type {\n kind: Ptr(\n Type {\n kind: Func {\n args: [\n Type {\n kind: Int(\n I256,\n ),\n ..\n },\n Type {\n kind: Int(\n I256,\n ),\n ..\n },\n ],\n ret_ty: Type {\n kind: Int(\n I32,\n ),\n ..\n },\n },\n ..\n },\n ),\n ..\n },\n ),\n ],\n ret_type: None,\n },\n blocks: [\n Block {\n id: BlockId {\n id: Some(\n 0,\n ),\n ..\n },\n stmts: [\n Stmt {\n kind: Inst(\n Inst {\n name: InstName {\n name: \"return\",\n ..\n },\n args: [],\n ..\n },\n ),\n ..\n },\n ],\n },\n ],\n comments: [],\n },\n ],\n comments: [],\n}" +input_file: test_files/syntax/module/func_type.sntn +--- +Module { + target: Some( + TargetTriple { + architecture: Evm, + vendor: Ethereum, + operating_system: Evm( + London, + ), + }, + ), + declared_functions: [], + struct_types: [], + functions: [ + Func { + signature: FuncSignature { + linkage: Public, + name: FunctionName { + name: "higher_order", + .. + }, + params: [ + ValueDeclaration( + ValueName { + string: "v0", + .. + }, + Type { + kind: Ptr( + Type { + kind: Func { + args: [ + Type { + kind: Int( + I256, + ), + .. + }, + Type { + kind: Int( + I256, + ), + .. + }, + ], + ret_ty: Type { + kind: Int( + I32, + ), + .. + }, + }, + .. + }, + ), + .. + }, + ), + ], + ret_type: None, + }, + blocks: [ + Block { + id: BlockId { + id: Some( + 0, + ), + .. + }, + stmts: [ + Stmt { + kind: Inst( + Inst { + name: InstName { + name: "return", + .. + }, + args: [], + .. + }, + ), + .. + }, + ], + }, + ], + comments: [], + }, + ], + comments: [], +} diff --git a/crates/parser/test_files/syntax/module/func_type.ir.snap b/crates/parser/test_files/syntax/module/func_type.ir.snap new file mode 100644 index 00000000..231e1b58 --- /dev/null +++ b/crates/parser/test_files/syntax/module/func_type.ir.snap @@ -0,0 +1,10 @@ +--- +source: crates/parser/tests/syntax.rs +expression: "target = evm-ethereum-london\nfunc public %higher_order(v0.*(i256, i256) -> i32) -> unit {\n block0:\n return;\n}\n\n" +input_file: test_files/syntax/module/func_type.sntn +--- +target = evm-ethereum-london +func public %higher_order(v0.*(i256, i256) -> i32) -> unit { + block0: + return; +} diff --git a/crates/parser/test_files/syntax/module/func_type.snap b/crates/parser/test_files/syntax/module/func_type.snap new file mode 100644 index 00000000..1cdb9a10 --- /dev/null +++ b/crates/parser/test_files/syntax/module/func_type.snap @@ -0,0 +1,46 @@ +--- +source: crates/parser/tests/syntax.rs +expression: "module \"target = \"evm-ethereum-london\"\n\nfunc public %higher_order(v0.*(i256, i256) -> i32) {\n block0:\n return;\n}\n\"\n target_triple \"evm-ethereum-london\"\n function \"func public %higher_order(v0.*(i256, i256) -> i32) {\n block0:\n return;\n }\"\n function_signature \"func public %higher_order(v0.*(i256, i256) -> i32) \"\n function_linkage \"public\"\n function_identifier \"%higher_order\"\n function_name \"higher_order\"\n function_params \"(v0.*(i256, i256) -> i32)\"\n value_declaration \"v0.*(i256, i256) -> i32\"\n value_name \"v0\"\n type_name \"*(i256, i256) -> i32\"\n ptr_type \"*(i256, i256) -> i32\"\n type_name \"(i256, i256) -> i32\"\n function_type \"(i256, i256) -> i32\"\n function_type_arg \"(i256, i256)\"\n type_name \"i256\"\n primitive_type \"i256\"\n type_name \"i256\"\n primitive_type \"i256\"\n function_ret_type \"-> i32\"\n type_name \"i32\"\n primitive_type \"i32\"\n block \"block0:\n return;\"\n block_ident \"block0\"\n block_number \"0\"\n stmt \"return;\"\n inst_stmt \"return\"\n inst \"return\"\n inst_name \"return\"\n inst_identifier \"return\"\n EOI \"\"\n" +input_file: test_files/syntax/module/func_type.sntn +--- +module "target = "evm-ethereum-london" + +func public %higher_order(v0.*(i256, i256) -> i32) { + block0: + return; +} +" + target_triple "evm-ethereum-london" + function "func public %higher_order(v0.*(i256, i256) -> i32) { + block0: + return; + }" + function_signature "func public %higher_order(v0.*(i256, i256) -> i32) " + function_linkage "public" + function_identifier "%higher_order" + function_name "higher_order" + function_params "(v0.*(i256, i256) -> i32)" + value_declaration "v0.*(i256, i256) -> i32" + value_name "v0" + type_name "*(i256, i256) -> i32" + ptr_type "*(i256, i256) -> i32" + type_name "(i256, i256) -> i32" + function_type "(i256, i256) -> i32" + function_type_arg "(i256, i256)" + type_name "i256" + primitive_type "i256" + type_name "i256" + primitive_type "i256" + function_ret_type "-> i32" + type_name "i32" + primitive_type "i32" + block "block0: + return;" + block_ident "block0" + block_number "0" + stmt "return;" + inst_stmt "return" + inst "return" + inst_name "return" + inst_identifier "return" + EOI "" diff --git a/crates/parser/test_files/syntax/module/func_type.sntn b/crates/parser/test_files/syntax/module/func_type.sntn new file mode 100644 index 00000000..48a6994a --- /dev/null +++ b/crates/parser/test_files/syntax/module/func_type.sntn @@ -0,0 +1,6 @@ +target = "evm-ethereum-london" + +func public %higher_order(v0.*(i256, i256) -> i32) { + block0: + return; +}