diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 790aa9b6ac..e759657ed8 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -519,7 +519,7 @@ where } Stmt::Join { parameters, - continuation, + body: continuation, remainder, .. } => { diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index d686f324e7..3eaafa5651 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -830,9 +830,10 @@ pub fn build_exp_call<'a, 'ctx, 'env>( CallType::HigherOrderLowLevel { op, - closure_layout, function_owns_closure_data, specialization_id, + arg_layouts, + ret_layout, .. } => { let bytes = specialization_id.to_bytes(); @@ -846,8 +847,9 @@ pub fn build_exp_call<'a, 'ctx, 'env>( scope, layout, *op, - *closure_layout, func_spec, + arg_layouts, + ret_layout, *function_owns_closure_data, arguments, ) @@ -1436,6 +1438,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( structure, wrapped: Wrapped::RecordOrSingleTagUnion, .. + } + | AccessAtIndex { + index, + structure, + wrapped: Wrapped::LikeARoseTree, + .. } => { // extract field from a record match load_symbol_and_layout(scope, structure) { @@ -2137,7 +2145,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( id, parameters, remainder, - continuation, + body: continuation, } => { let builder = env.builder; let context = env.context; @@ -3161,7 +3169,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( let it = procedures.iter().map(|x| x.1); let solutions = match roc_mono::alias_analysis::spec_program(entry_point, it) { - Err(e) => panic!("Error in alias analysis: {:?}", e), + Err(e) => panic!("Error in alias analysis: {}", e), Ok(solutions) => solutions, }; @@ -3815,8 +3823,9 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( scope: &Scope<'a, 'ctx>, return_layout: &Layout<'a>, op: LowLevel, - function_layout: Layout<'a>, func_spec: FuncSpec, + argument_layouts: &[Layout<'a>], + result_layout: &Layout<'a>, function_owns_closure_data: bool, args: &[Symbol], ) -> BasicValueEnum<'ctx> { @@ -3828,6 +3837,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( macro_rules! passed_function_at_index { ($index:expr) => {{ let function_symbol = args[$index]; + let function_layout = Layout::FunctionPointer(argument_layouts, return_layout); function_value_by_func_spec(env, func_spec, function_symbol, function_layout) }}; @@ -4095,7 +4105,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( env, layout_ids, roc_function_call, - &function_layout, + result_layout, list, before_layout, after_layout, @@ -4139,7 +4149,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( env, layout_ids, roc_function_call, - &function_layout, + result_layout, list, before_layout, after_layout, diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index cf2044a8d0..e3b9901a0e 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -601,18 +601,12 @@ pub fn list_keep_oks<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, roc_function_call: RocFunctionCall<'ctx>, - function_layout: &Layout<'a>, + // Layout of the `Result after *` + result_layout: &Layout<'a>, list: BasicValueEnum<'ctx>, before_layout: &Layout<'a>, after_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - // Layout of the `Result after *` - let result_layout = match function_layout { - Layout::FunctionPointer(_, ret) => ret, - Layout::Closure(_, _, ret) => ret, - _ => unreachable!("not a callable layout"), - }; - let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout); call_bitcode_fn( @@ -638,18 +632,12 @@ pub fn list_keep_errs<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, roc_function_call: RocFunctionCall<'ctx>, - function_layout: &Layout<'a>, + // Layout of the `Result * err` + result_layout: &Layout<'a>, list: BasicValueEnum<'ctx>, before_layout: &Layout<'a>, after_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - // Layout of the `Result after *` - let result_layout = match function_layout { - Layout::FunctionPointer(_, ret) => ret, - Layout::Closure(_, _, ret) => ret, - _ => unreachable!("not a callable layout"), - }; - let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout); call_bitcode_fn( diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index c3f3b142e8..845b0313c4 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -19,19 +19,22 @@ pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STA const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; -pub fn func_name_bytes(proc: &Proc) -> [u8; 16] { +pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] { func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), proc.ret_layout) } +const DEBUG: bool = false; +const SIZE: usize = if DEBUG { 50 } else { 16 }; + pub fn func_name_bytes_help<'a, I>( symbol: Symbol, argument_layouts: I, return_layout: Layout<'a>, -) -> [u8; 16] +) -> [u8; SIZE] where I: Iterator>, { - let mut name_bytes = [0u8; 16]; + let mut name_bytes = [0u8; SIZE]; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; @@ -75,9 +78,27 @@ where *target = *source; } + if DEBUG { + for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() { + name_bytes[25 + i] = c as u8; + } + } + name_bytes } +fn bytes_as_ascii(bytes: &[u8]) -> String { + use std::fmt::Write; + + let mut buf = String::new(); + + for byte in bytes { + write!(buf, "{:02X}", byte).unwrap(); + } + + buf +} + pub fn spec_program<'a, I>( entry_point: crate::ir::EntryPoint<'a>, procs: I, @@ -102,15 +123,34 @@ where m.add_const(STATIC_STR_NAME, static_str_def)?; // the entry point wrapper + let roc_main_bytes = func_name_bytes_help( + entry_point.symbol, + entry_point.layout.arguments.iter().copied(), + entry_point.layout.result, + ); + let roc_main = FuncName(&roc_main_bytes); + + let entry_point_function = build_entry_point(entry_point.layout, roc_main)?; let entry_point_name = FuncName(ENTRY_POINT_NAME); - let entry_point_function = build_entry_point(entry_point.layout, entry_point_name)?; m.add_func(entry_point_name, entry_point_function)?; // all other functions for proc in procs { + let bytes = func_name_bytes(proc); + let func_name = FuncName(&bytes); + + if DEBUG { + eprintln!( + "{:?}: {:?} with {:?} args", + proc.name, + bytes_as_ascii(&bytes), + proc.args.len() + ); + } + let spec = proc_spec(proc)?; - m.add_func(FuncName(&func_name_bytes(proc)), spec)?; + m.add_func(func_name, spec)?; } m.build()? @@ -126,7 +166,9 @@ where p.build()? }; - // eprintln!("{}", program.to_source_string()); + if DEBUG { + eprintln!("{}", program.to_source_string()); + } morphic_lib::solve(program) } @@ -198,11 +240,25 @@ fn stmt_spec( use Stmt::*; match stmt { - Let(symbol, expr, layout, continuation) => { - let value_id = expr_spec(builder, env, block, layout, expr)?; + Let(symbol, expr, expr_layout, mut continuation) => { + let value_id = expr_spec(builder, env, block, expr_layout, expr)?; env.symbols.insert(*symbol, value_id); + + let mut queue = vec![symbol]; + + while let Let(symbol, expr, expr_layout, c) = continuation { + let value_id = expr_spec(builder, env, block, expr_layout, expr)?; + env.symbols.insert(*symbol, value_id); + + queue.push(symbol); + continuation = c; + } + let result = stmt_spec(builder, env, block, layout, continuation)?; - env.symbols.remove(symbol); + + for symbol in queue { + env.symbols.remove(symbol); + } Ok(result) } @@ -235,7 +291,7 @@ fn stmt_spec( cond_layout: _, branches, default_branch, - ret_layout, + ret_layout: _lies, } => { let mut cases = Vec::with_capacity(branches.len() + 1); @@ -246,7 +302,7 @@ fn stmt_spec( for branch in it { let block = builder.add_block(); - let value_id = stmt_spec(builder, env, block, ret_layout, branch)?; + let value_id = stmt_spec(builder, env, block, layout, branch)?; cases.push(BlockExpr(block, value_id)); } @@ -282,7 +338,7 @@ fn stmt_spec( Join { id, parameters, - continuation, + body, remainder, } => { let mut type_ids = Vec::new(); @@ -298,27 +354,33 @@ fn stmt_spec( let (jpid, jp_argument) = builder.declare_continuation(block, jp_arg_type_id, ret_type_id)?; + // NOTE join point arguments can shadow variables from the outer scope + // the ordering of steps here is important + + // add this ID so both body and remainder can reference it + env.join_points.insert(*id, jpid); + + // first, with the current variable bindings, process the remainder + let cont_block = builder.add_block(); + let cont_value_id = stmt_spec(builder, env, cont_block, layout, remainder)?; + + // only then introduce variables bound by the jump point, and process its body let join_body_sub_block = { - env.join_points.insert(*id, jpid); let jp_body_block = builder.add_block(); // unpack the argument for (i, p) in parameters.iter().enumerate() { let value_id = builder.add_get_tuple_field(jp_body_block, jp_argument, i as u32)?; + env.symbols.insert(p.symbol, value_id); } - let jp_body_value_id = stmt_spec(builder, env, jp_body_block, layout, remainder)?; + let jp_body_value_id = stmt_spec(builder, env, jp_body_block, layout, body)?; + BlockExpr(jp_body_block, jp_body_value_id) }; - // NOTE the symbols bound by the join point can shadow the argument symbols of the - // surrounding function, so we don't remove them from the env here - - let cont_block = builder.add_block(); - let cont_value_id = stmt_spec(builder, env, cont_block, layout, continuation)?; - env.join_points.remove(id); builder.define_continuation(jpid, join_body_sub_block)?; @@ -423,7 +485,7 @@ fn call_spec( ), HigherOrderLowLevel { specialization_id, - closure_layout: _, + closure_env_layout, op, arg_layouts, ret_layout, @@ -458,15 +520,160 @@ fn call_spec( DictWalk => { let dict = env.symbols[&call.arguments[0]]; let default = env.symbols[&call.arguments[1]]; + let closure_env = env.symbols[&call.arguments[3]]; let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?; let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; let first = builder.add_bag_get(block, bag)?; - let argument = builder.add_make_tuple(block, &[first, default])?; + let key = builder.add_get_tuple_field(block, first, 0)?; + let val = builder.add_get_tuple_field(block, first, 1)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[key, val, default])? + } else { + builder.add_make_tuple(block, &[key, val, default, closure_env])? + }; builder.add_call(block, spec_var, module, name, argument)?; } + + ListWalk | ListWalkBackwards | ListWalkUntil => { + let list = env.symbols[&call.arguments[0]]; + let default = env.symbols[&call.arguments[1]]; + let closure_env = env.symbols[&call.arguments[3]]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let first = builder.add_bag_get(block, bag)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[first, default])? + } else { + builder.add_make_tuple(block, &[first, default, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMapWithIndex => { + let list = env.symbols[&call.arguments[0]]; + let closure_env = env.symbols[&call.arguments[2]]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let first = builder.add_bag_get(block, bag)?; + let index = builder.add_make_tuple(block, &[])?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[first, index])? + } else { + builder.add_make_tuple(block, &[first, index, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMap => { + let list1 = env.symbols[&call.arguments[0]]; + let closure_env = env.symbols[&call.arguments[2]]; + + let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; + + let elem1 = builder.add_bag_get(block, bag1)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1])? + } else { + builder.add_make_tuple(block, &[elem1, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListSortWith => { + let list1 = env.symbols[&call.arguments[0]]; + let closure_env = env.symbols[&call.arguments[2]]; + + let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; + + let elem1 = builder.add_bag_get(block, bag1)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1, elem1])? + } else { + builder.add_make_tuple(block, &[elem1, elem1, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMap2 => { + let list1 = env.symbols[&call.arguments[0]]; + let list2 = env.symbols[&call.arguments[1]]; + let closure_env = env.symbols[&call.arguments[3]]; + + let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; + let elem1 = builder.add_bag_get(block, bag1)?; + + let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; + let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; + let elem2 = builder.add_bag_get(block, bag2)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1, elem2])? + } else { + builder.add_make_tuple(block, &[elem1, elem2, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMap3 => { + let list1 = env.symbols[&call.arguments[0]]; + let list2 = env.symbols[&call.arguments[1]]; + let list3 = env.symbols[&call.arguments[2]]; + let closure_env = env.symbols[&call.arguments[4]]; + + let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; + let elem1 = builder.add_bag_get(block, bag1)?; + + let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; + let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; + let elem2 = builder.add_bag_get(block, bag2)?; + + let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?; + let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?; + let elem3 = builder.add_bag_get(block, bag3)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1, elem2, elem3])? + } else { + builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListKeepIf | ListKeepOks | ListKeepErrs => { + let list = env.symbols[&call.arguments[0]]; + let closure_env = env.symbols[&call.arguments[2]]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + // let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let first = builder.add_bag_get(block, bag)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[first])? + } else { + builder.add_make_tuple(block, &[first, closure_env])? + }; + let result = builder.add_call(block, spec_var, module, name, argument)?; + let unit = builder.add_tuple_type(&[])?; + builder.add_unknown_with(block, &[result], unit)?; + } + _ => { // fake a call to the function argument // to make sure the function is specialized @@ -653,7 +860,7 @@ fn expr_spec( Struct(fields) => build_tuple_value(builder, env, block, fields), AccessAtIndex { index, - field_layouts, + field_layouts: _, structure, wrapped, } => { @@ -673,17 +880,20 @@ fn expr_spec( Wrapped::RecordOrSingleTagUnion => { builder.add_get_tuple_field(block, value_id, *index as u32) } + Wrapped::LikeARoseTree => { + let result_type = layout_spec(builder, layout)?; + builder.add_unknown_with(block, &[value_id], result_type) + } Wrapped::MultiTagUnion => { // Clearly this is not generally correct, but it should be for our examples - let hacky_is_recursive = - field_layouts.iter().any(|l| l == &Layout::RecursivePointer); + // let hacky_is_recursive = field_layouts.iter().any(|l| l == &Layout::RecursivePointer); + // if hacky_is_recursive { - if hacky_is_recursive { - let result_type = layout_spec(builder, layout)?; - builder.add_unknown_with(block, &[value_id], result_type) - } else { - builder.add_get_tuple_field(block, value_id, *index as u32) - } + // we don't know what constructor we are at this point, so how can we get a + // field from an enum value? + + let result_type = layout_spec(builder, layout)?; + builder.add_unknown_with(block, &[value_id], result_type) } } } @@ -700,7 +910,9 @@ fn expr_spec( bag = builder.add_bag_insert(block, bag, value_id)?; } - Ok(bag) + let cell = builder.add_new_heap_cell(block)?; + + builder.add_make_tuple(block, &[cell, bag]) } EmptyArray => { @@ -827,8 +1039,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result(builder: &mut TC) -> Result { let cell_id = builder.add_heap_cell_type(); - let len_id = builder.add_tuple_type(&[])?; - builder.add_tuple_type(&[cell_id, len_id]) + builder.add_tuple_type(&[cell_id]) } // const OK_TAG_ID: u8 = 1u8; diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 963ab1e2c6..b919afe510 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -193,7 +193,7 @@ impl<'a> ParamMap<'a> { id: j, parameters: xs, remainder: v, - continuation: b, + body: b, } => { let already_in_there = self .items @@ -393,15 +393,20 @@ impl<'a> BorrowInfState<'a> { } HigherOrderLowLevel { - op, closure_layout, .. + op, + arg_layouts, + ret_layout, + .. } => { use roc_module::low_level::LowLevel::*; debug_assert!(op.is_higher_order()); + let closure_layout = Layout::FunctionPointer(arg_layouts, ret_layout); + match op { ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => { - match self.param_map.get_symbol(arguments[1], *closure_layout) { + match self.param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // own the list if the function wants to own the element if !function_ps[0].borrow { @@ -417,7 +422,7 @@ impl<'a> BorrowInfState<'a> { } } ListMapWithIndex => { - match self.param_map.get_symbol(arguments[1], *closure_layout) { + match self.param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // own the list if the function wants to own the element if !function_ps[1].borrow { @@ -432,7 +437,7 @@ impl<'a> BorrowInfState<'a> { None => unreachable!(), } } - ListMap2 => match self.param_map.get_symbol(arguments[2], *closure_layout) { + ListMap2 => match self.param_map.get_symbol(arguments[2], closure_layout) { Some(function_ps) => { // own the lists if the function wants to own the element if !function_ps[0].borrow { @@ -450,7 +455,7 @@ impl<'a> BorrowInfState<'a> { } None => unreachable!(), }, - ListMap3 => match self.param_map.get_symbol(arguments[3], *closure_layout) { + ListMap3 => match self.param_map.get_symbol(arguments[3], closure_layout) { Some(function_ps) => { // own the lists if the function wants to own the element if !function_ps[0].borrow { @@ -471,7 +476,7 @@ impl<'a> BorrowInfState<'a> { None => unreachable!(), }, ListSortWith => { - match self.param_map.get_symbol(arguments[1], *closure_layout) { + match self.param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // always own the input list self.own_var(arguments[0]); @@ -485,7 +490,7 @@ impl<'a> BorrowInfState<'a> { } } ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { - match self.param_map.get_symbol(arguments[2], *closure_layout) { + match self.param_map.get_symbol(arguments[2], closure_layout) { Some(function_ps) => { // own the data structure if the function wants to own the element if !function_ps[0].borrow { @@ -618,7 +623,7 @@ impl<'a> BorrowInfState<'a> { id: j, parameters: ys, remainder: v, - continuation: b, + body: b, } => { let old = self.param_set.clone(); self.update_param_set(ys); diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 565e9f7126..b1a29b9be2 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -597,7 +597,7 @@ fn to_relevant_branch_help<'a>( start.extend(end); } } - Wrapped::RecordOrSingleTagUnion => { + Wrapped::RecordOrSingleTagUnion | Wrapped::LikeARoseTree => { let sub_positions = arguments.into_iter().enumerate().map( |(index, (pattern, _))| { let mut new_path = path.to_vec(); @@ -956,7 +956,7 @@ pub fn optimize_when<'a>( stmt = Stmt::Join { id, parameters: &[], - continuation: env.arena.alloc(body), + body: env.arena.alloc(body), remainder: env.arena.alloc(stmt), }; } @@ -1329,7 +1329,7 @@ fn compile_guard<'a>( id, parameters: arena.alloc([param]), remainder: stmt, - continuation: arena.alloc(cond), + body: arena.alloc(cond), } } @@ -1622,7 +1622,7 @@ fn decide_to_branching<'a>( Stmt::Join { id: fail_jp_id, parameters: &[], - continuation: fail, + body: fail, remainder: arena.alloc(test_stmt), } } diff --git a/compiler/mono/src/expand_rc.rs b/compiler/mono/src/expand_rc.rs index 1686bfc123..2d60e6a213 100644 --- a/compiler/mono/src/expand_rc.rs +++ b/compiler/mono/src/expand_rc.rs @@ -622,7 +622,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt< Join { id, parameters, - continuation, + body: continuation, remainder, } => { let continuation = expand_and_cancel(env, continuation); @@ -631,7 +631,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt< let stmt = Join { id: *id, parameters, - continuation, + body: continuation, remainder, }; diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index eb7fa7903a..d2dcd32092 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -64,7 +64,7 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet, MutSet) Join { parameters, - continuation, + body: continuation, remainder, .. } => { @@ -455,7 +455,7 @@ impl<'a> Context<'a> { HigherOrderLowLevel { op, - closure_layout, + closure_env_layout, specialization_id, arg_layouts, ret_layout, @@ -467,7 +467,7 @@ impl<'a> Context<'a> { call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { HigherOrderLowLevel { op: *op, - closure_layout: *closure_layout, + closure_env_layout: *closure_env_layout, function_owns_closure_data: true, specialization_id: *specialization_id, arg_layouts, @@ -497,12 +497,14 @@ impl<'a> Context<'a> { const FUNCTION: bool = BORROWED; const CLOSURE_DATA: bool = BORROWED; + let function_layout = Layout::FunctionPointer(arg_layouts, ret_layout); + match op { roc_module::low_level::LowLevel::ListMap | roc_module::low_level::LowLevel::ListKeepIf | roc_module::low_level::LowLevel::ListKeepOks | roc_module::low_level::LowLevel::ListKeepErrs => { - match self.param_map.get_symbol(arguments[1], *closure_layout) { + match self.param_map.get_symbol(arguments[1], function_layout) { Some(function_ps) => { let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; @@ -524,7 +526,7 @@ impl<'a> Context<'a> { } } roc_module::low_level::LowLevel::ListMapWithIndex => { - match self.param_map.get_symbol(arguments[1], *closure_layout) { + match self.param_map.get_symbol(arguments[1], function_layout) { Some(function_ps) => { let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; @@ -545,7 +547,7 @@ impl<'a> Context<'a> { } } roc_module::low_level::LowLevel::ListMap2 => { - match self.param_map.get_symbol(arguments[2], *closure_layout) { + match self.param_map.get_symbol(arguments[2], function_layout) { Some(function_ps) => { let borrows = [ function_ps[0].borrow, @@ -572,7 +574,7 @@ impl<'a> Context<'a> { } } roc_module::low_level::LowLevel::ListMap3 => { - match self.param_map.get_symbol(arguments[3], *closure_layout) { + match self.param_map.get_symbol(arguments[3], function_layout) { Some(function_ps) => { let borrows = [ function_ps[0].borrow, @@ -601,7 +603,7 @@ impl<'a> Context<'a> { } } roc_module::low_level::LowLevel::ListSortWith => { - match self.param_map.get_symbol(arguments[1], *closure_layout) { + match self.param_map.get_symbol(arguments[1], function_layout) { Some(function_ps) => { let borrows = [OWNED, FUNCTION, CLOSURE_DATA]; @@ -623,7 +625,7 @@ impl<'a> Context<'a> { | roc_module::low_level::LowLevel::ListWalkUntil | roc_module::low_level::LowLevel::ListWalkBackwards | roc_module::low_level::LowLevel::DictWalk => { - match self.param_map.get_symbol(arguments[2], *closure_layout) { + match self.param_map.get_symbol(arguments[2], function_layout) { Some(function_ps) => { // borrow data structure based on first argument of the folded function // borrow the default based on second argument of the folded function @@ -978,7 +980,7 @@ impl<'a> Context<'a> { id: j, parameters: _, remainder: b, - continuation: v, + body: v, } => { // get the parameters with borrow signature let xs = self.param_map.get_join_point(*j); @@ -1000,7 +1002,7 @@ impl<'a> Context<'a> { id: *j, parameters: xs, remainder: b, - continuation: v, + body: v, }), b_live_vars, ) @@ -1143,7 +1145,7 @@ pub fn collect_stmt( id: j, parameters, remainder: b, - continuation: v, + body: v, } => { let mut j_live_vars = collect_stmt(v, jp_live_vars, MutSet::default()); for param in parameters.iter() { diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 686b5b8b53..fdb5db4f76 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -890,9 +890,10 @@ pub enum Stmt<'a> { Join { id: JoinPointId, parameters: &'a [Param<'a>], - /// does not contain jumps to this id - continuation: &'a Stmt<'a>, - /// the "body" of the join point, contains the jumps to this id + /// body of the join point + /// what happens after _jumping to_ the join point + body: &'a Stmt<'a>, + /// what happens after _defining_ the join point remainder: &'a Stmt<'a>, }, Jump(JoinPointId, &'a [Symbol]), @@ -1013,6 +1014,8 @@ pub enum Wrapped { EmptyRecord, SingleElementRecord, RecordOrSingleTagUnion, + /// Like a rose tree; recursive, but only one tag + LikeARoseTree, MultiTagUnion, } @@ -1045,7 +1048,7 @@ impl Wrapped { }, _ => Some(Wrapped::MultiTagUnion), }, - NonNullableUnwrapped(_) => Some(Wrapped::RecordOrSingleTagUnion), + NonNullableUnwrapped(_) => Some(Wrapped::LikeARoseTree), NullableWrapped { .. } | NullableUnwrapped { .. } => { Some(Wrapped::MultiTagUnion) @@ -1151,7 +1154,7 @@ pub enum CallType<'a> { HigherOrderLowLevel { op: LowLevel, /// the layout of the closure argument, if any - closure_layout: Layout<'a>, + closure_env_layout: Option>, /// specialization id of the function argument specialization_id: CallSpecId, /// does the function need to own the closure data @@ -1476,7 +1479,7 @@ impl<'a> Stmt<'a> { Join { id, parameters, - continuation, + body: continuation, remainder, } => { let it = parameters.iter().map(|p| symbol_to_doc(alloc, p.symbol)); @@ -2712,8 +2715,6 @@ macro_rules! match_on_closure_argument { let arena = $env.arena; - let function_layout = arena.alloc(top_level).full(); - let arg_layouts = top_level.arguments; let ret_layout = top_level.result; @@ -2723,10 +2724,10 @@ macro_rules! match_on_closure_argument { $env, lambda_set, $closure_data_symbol, - |top_level_function, closure_data, function_layout, specialization_id| self::Call { + |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { call_type: CallType::HigherOrderLowLevel { op: $op, - closure_layout: function_layout, + closure_env_layout, specialization_id, function_owns_closure_data: false, arg_layouts, @@ -2734,7 +2735,6 @@ macro_rules! match_on_closure_argument { }, arguments: arena.alloc([$($x,)* top_level_function, closure_data]), }, - function_layout, $layout, $assigned, $hole, @@ -3328,7 +3328,7 @@ pub fn with_hole<'a>( id, parameters: env.arena.alloc([param]), remainder: env.arena.alloc(stmt), - continuation: hole, + body: hole, } } } @@ -3381,7 +3381,7 @@ pub fn with_hole<'a>( id, parameters: env.arena.alloc([param]), remainder: env.arena.alloc(stmt), - continuation: env.arena.alloc(hole), + body: env.arena.alloc(hole), } } @@ -4275,7 +4275,10 @@ fn convert_tag_union<'a>( ) } - Unwrapped(_, field_layouts) => { + Unwrapped { + arguments: field_layouts, + .. + } => { let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args); let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena); @@ -5327,7 +5330,7 @@ fn substitute_in_stmt_help<'a>( id, parameters, remainder, - continuation, + body: continuation, } => { let opt_remainder = substitute_in_stmt_help(arena, remainder, subs); let opt_continuation = substitute_in_stmt_help(arena, continuation, subs); @@ -5340,7 +5343,7 @@ fn substitute_in_stmt_help<'a>( id: *id, parameters, remainder, - continuation, + body: continuation, })) } else { None @@ -7013,7 +7016,10 @@ fn from_can_pattern_help<'a>( union, } } - Unwrapped(_, field_layouts) => { + Unwrapped { + arguments: field_layouts, + .. + } => { let union = crate::exhaustive::Union { render_as: RenderAs::Tag, alternatives: vec![Ctor { @@ -7708,13 +7714,12 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>( lambda_set: LambdaSet<'a>, closure_data_symbol: Symbol, to_lowlevel_call: ToLowLevelCall, - function_layout: Layout<'a>, return_layout: Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> where - ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy, + ToLowLevelCall: Fn(Symbol, Symbol, Option>, CallSpecId) -> Call<'a> + Copy, { match lambda_set.runtime_representation() { Layout::Union(_) => { @@ -7726,8 +7731,8 @@ where closure_tag_id_symbol, Layout::Builtin(crate::layout::TAG_SIZE), closure_data_symbol, + lambda_set.is_represented(), to_lowlevel_call, - function_layout, return_layout, assigned, hole, @@ -7755,7 +7760,7 @@ where let call = to_lowlevel_call( function_symbol, closure_data_symbol, - function_layout, + lambda_set.is_represented(), call_spec_id, ); @@ -7770,8 +7775,8 @@ where closure_tag_id_symbol, Layout::Builtin(Builtin::Int1), closure_data_symbol, + lambda_set.is_represented(), to_lowlevel_call, - function_layout, return_layout, assigned, hole, @@ -7786,8 +7791,8 @@ where closure_tag_id_symbol, Layout::Builtin(Builtin::Int8), closure_data_symbol, + lambda_set.is_represented(), to_lowlevel_call, - function_layout, return_layout, assigned, hole, @@ -7804,14 +7809,14 @@ fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>( closure_tag_id_symbol: Symbol, closure_tag_id_layout: Layout<'a>, closure_data_symbol: Symbol, + closure_env_layout: Option>, to_lowlevel_call: ToLowLevelCall, - function_layout: Layout<'a>, return_layout: Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> where - ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy, + ToLowLevelCall: Fn(Symbol, Symbol, Option>, CallSpecId) -> Call<'a> + Copy, { debug_assert!(!lambda_set.is_empty()); @@ -7828,7 +7833,7 @@ where let call = to_lowlevel_call( *function_symbol, closure_data_symbol, - function_layout, + closure_env_layout, call_spec_id, ); let stmt = build_call(env, call, assigned, return_layout, env.arena.alloc(hole)); @@ -7859,7 +7864,7 @@ where Stmt::Join { id: join_point_id, parameters: &*env.arena.alloc([param]), - continuation: hole, + body: hole, remainder: env.arena.alloc(switch), } } @@ -8017,7 +8022,7 @@ fn union_lambda_set_to_switch<'a>( Stmt::Join { id: join_point_id, parameters: &*env.arena.alloc([param]), - continuation: hole, + body: hole, remainder: env.arena.alloc(switch), } } @@ -8161,7 +8166,7 @@ fn enum_lambda_set_to_switch<'a>( Stmt::Join { id: join_point_id, parameters: &*env.arena.alloc([param]), - continuation: hole, + body: hole, remainder: env.arena.alloc(switch), } } @@ -8229,14 +8234,14 @@ fn lowlevel_enum_lambda_set_to_switch<'a, ToLowLevelCall>( closure_tag_id_symbol: Symbol, closure_tag_id_layout: Layout<'a>, closure_data_symbol: Symbol, + closure_env_layout: Option>, to_lowlevel_call: ToLowLevelCall, - function_layout: Layout<'a>, return_layout: Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> where - ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy, + ToLowLevelCall: Fn(Symbol, Symbol, Option>, CallSpecId) -> Call<'a> + Copy, { debug_assert!(!lambda_set.is_empty()); @@ -8253,7 +8258,7 @@ where let call = to_lowlevel_call( *function_symbol, closure_data_symbol, - function_layout, + closure_env_layout, call_spec_id, ); let stmt = build_call( @@ -8290,7 +8295,7 @@ where Stmt::Join { id: join_point_id, parameters: &*env.arena.alloc([param]), - continuation: hole, + body: hole, remainder: env.arena.alloc(switch), } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 5a8a3f75e4..44e6854cd5 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -143,6 +143,14 @@ impl<'a> LambdaSet<'a> { *self.representation } + pub fn is_represented(&self) -> Option> { + if let Layout::Struct(&[]) = self.representation { + None + } else { + Some(*self.representation) + } + } + pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> { debug_assert!( self.set.iter().any(|(s, _)| *s == function_symbol), @@ -281,7 +289,9 @@ impl<'a> LambdaSet<'a> { Unit | UnitWithArguments => Layout::Struct(&[]), BoolUnion { .. } => Layout::Builtin(Builtin::Int1), ByteUnion(_) => Layout::Builtin(Builtin::Int8), - Unwrapped(_tag_name, layouts) => Layout::Struct(layouts.into_bump_slice()), + Unwrapped { + arguments: layouts, .. + } => Layout::Struct(layouts.into_bump_slice()), Wrapped(variant) => { use WrappedVariant::*; @@ -1266,9 +1276,15 @@ pub enum UnionVariant<'a> { Never, Unit, UnitWithArguments, - BoolUnion { ttrue: TagName, ffalse: TagName }, + BoolUnion { + ttrue: TagName, + ffalse: TagName, + }, ByteUnion(Vec<'a, TagName>), - Unwrapped(TagName, Vec<'a, Layout<'a>>), + Unwrapped { + tag_name: TagName, + arguments: Vec<'a, Layout<'a>>, + }, Wrapped(WrappedVariant<'a>), } @@ -1486,7 +1502,10 @@ pub fn union_sorted_tags_help<'a>( fields: layouts.into_bump_slice(), }) } else { - UnionVariant::Unwrapped(tag_name, layouts) + UnionVariant::Unwrapped { + tag_name, + arguments: layouts, + } } } num_tags => { @@ -1638,7 +1657,10 @@ pub fn layout_from_tag_union<'a>( Unit | UnitWithArguments => Layout::Struct(&[]), BoolUnion { .. } => Layout::Builtin(Builtin::Int1), ByteUnion(_) => Layout::Builtin(Builtin::Int8), - Unwrapped(_, mut field_layouts) => { + Unwrapped { + arguments: mut field_layouts, + .. + } => { if field_layouts.len() == 1 { field_layouts.pop().unwrap() } else { diff --git a/compiler/mono/src/tail_recursion.rs b/compiler/mono/src/tail_recursion.rs index 618acf81f3..0eae4df30a 100644 --- a/compiler/mono/src/tail_recursion.rs +++ b/compiler/mono/src/tail_recursion.rs @@ -60,7 +60,7 @@ pub fn make_tail_recursive<'a>( id, remainder: jump, parameters: params, - continuation: new, + body: new, } } } @@ -160,7 +160,7 @@ fn insert_jumps<'a>( id, parameters, remainder, - continuation, + body: continuation, } => { let opt_remainder = insert_jumps(arena, remainder, goal_id, needle); let opt_continuation = insert_jumps(arena, continuation, goal_id, needle); @@ -173,7 +173,7 @@ fn insert_jumps<'a>( id: *id, parameters, remainder, - continuation, + body: continuation, })) } else { None diff --git a/examples/quicksort/platform/Package-Config.roc b/examples/quicksort/platform/Package-Config.roc index 2c5757a71e..9569118e7b 100644 --- a/examples/quicksort/platform/Package-Config.roc +++ b/examples/quicksort/platform/Package-Config.roc @@ -6,7 +6,5 @@ platform examples/quicksort provides [ mainForHost ] effects fx.Effect {} -update : Model -> Model - mainForHost : List I64 -> List I64 mainForHost = \list -> quicksort list diff --git a/vendor/morphic_lib/src/api.rs b/vendor/morphic_lib/src/api.rs index d220904f97..f45c2dbd53 100644 --- a/vendor/morphic_lib/src/api.rs +++ b/vendor/morphic_lib/src/api.rs @@ -2,6 +2,7 @@ use sha2::{digest::Digest, Sha256}; use smallvec::SmallVec; use std::collections::{btree_map::Entry, BTreeMap}; +use crate::preprocess; use crate::render_api_ir; use crate::util::blocks::Blocks; use crate::util::id_bi_map::IdBiMap; @@ -57,6 +58,8 @@ enum ErrorKind { ModNotFound(ModNameBuf), #[error("entry point {0:?} not found in program")] EntryPointNotFound(EntryPointNameBuf), + #[error("{0}")] + PreprocessError(preprocess::Error), } #[derive(Clone, thiserror::Error, Debug)] @@ -1550,6 +1553,8 @@ fn populate_specs( } pub fn solve(program: Program) -> Result { + preprocess::preprocess(&program).map_err(ErrorKind::PreprocessError)?; + Ok(Solutions { mods: program .mods diff --git a/vendor/morphic_lib/src/ir.rs b/vendor/morphic_lib/src/ir.rs new file mode 100644 index 0000000000..b6408ebc98 --- /dev/null +++ b/vendor/morphic_lib/src/ir.rs @@ -0,0 +1,303 @@ +//! The core IR used for analysis. +//! +//! The IR uses a variant of SSA in which control-dependent values are represented using block +//! parameters instead of phi nodes. + +use smallvec::SmallVec; + +use crate::api::{CalleeSpecVarId, UpdateModeVarId}; +use crate::name_cache::{ConstId, FuncId}; +use crate::type_cache::TypeId; +use crate::util::blocks::Blocks; +use crate::util::flat_slices::{FlatSlices, Slice}; +use crate::util::op_graph::OpGraph; +use crate::util::strongly_connected::{strongly_connected, SccKind}; + +/// The "core payload" of a node in the op graph, determining its semantics. +/// +/// The `OpKind` of a node is *not* sufficient to uniquely determine either the ids or the types of +/// its inputs or output. This information is stored separately in the graph. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum OpKind { + // Variadic inputs + // Inputs may have any type + // Output may have any type + UnknownWith, + // 1 input + // Input type must match argument type of callee + // Output type must match return type of callee + Call { + callee_spec_var: CalleeSpecVarId, + callee: FuncId, + }, + // 0 inputs + // Output type must match type of const + ConstRef { + const_: ConstId, + }, + // 0 inputs + // Output type is heap cell + NewHeapCell, + // 1 input + // Input may have any type + // Output type is unit + RecursiveTouch, + // 1 input + // Input type is heap cell + // Output type is unit + UpdateWriteOnly { + update_mode_var: UpdateModeVarId, + }, + // 0 inputs + // Output type is `Bag` for some `T` + EmptyBag, + // 2 inputs: `(bag, to_insert)` + // `bag` has type `Bag` + // `to_insert` has type `T` + // Output type is `Bag` + BagInsert, + // 1 input + // Input type is `Bag` + // Output type is `T` + BagGet, + // 1 input + // Input type is `Bag` + // Output type is `(Bag, T)` + BagRemove, + // Variadic inputs + // Input types are `T_1, ..., T_N` + // Output type is `(T_1, ..., T_N)` + MakeTuple, + // 1 input + // Input type is `(T_1, ..., T_N)` + // Output type is `T_(field_idx)` + GetTupleField { + field_idx: u32, + }, + // 1 input + // Input type is `T_(variant_idx)` + // Output type is `union { T_1, ..., T_N }` + MakeUnion { + variant_idx: u32, + }, + // 1 input + // Input type is `union { T_1, ..., T_N }` + // Output type is `T_(variant_idx)` + UnwrapUnion { + variant_idx: u32, + }, + // 1 input + // Input type is content type of a named type `T` + // Output type is `T` + MakeNamed, + // 1 input + // Input type is a named type `T` + // Output type is the content type of `T` + UnwrapNamed, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum ValueKind { + Op(OpKind), + // 0 inputs + BlockParam, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct ValueInfo { + pub(crate) kind: ValueKind, + pub(crate) result_type: TypeId, +} + +id_type! { + pub(crate) ValueId(u32); +} + +id_type! { + pub(crate) BlockId(u32); +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum JumpTarget { + Block(BlockId), + Ret, +} + +pub(crate) const JUMP_TARGETS_INLINE_COUNT: usize = 8; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct BlockInfo { + /// A block may optionally have a single parameter, which serves the same role as a phi node in + /// SSA. + /// + /// Invariant: If `param` is `Some`, it must point to a `BlockParam` value, not an `Op`. + pub(crate) param: Option, + /// List of zero or more jump targets to nondeterministically choose from. + pub(crate) jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, + /// Optional argument which will be passed to the chosen jump target. + pub(crate) target_arg: Option, +} + +id_type! { + /// Identifies a strongly connected component in the control flow graph. + /// + /// https://en.wikipedia.org/wiki/Strongly_connected_component + pub(crate) SccId(u32); +} + +#[derive(Clone, Debug)] +pub(crate) struct GraphBuilder { + values: OpGraph, + blocks: Blocks, +} + +impl GraphBuilder { + pub(crate) fn new() -> Self { + GraphBuilder { + values: OpGraph::new(), + blocks: Blocks::new(), + } + } + + pub(crate) fn add_op( + &mut self, + block: BlockId, + op_kind: OpKind, + inputs: &[ValueId], + result_type: TypeId, + ) -> ValueId { + let val_id = self.values.add_node( + ValueInfo { + kind: ValueKind::Op(op_kind), + result_type, + }, + inputs, + ); + self.blocks.add_value(block, val_id); + val_id + } + + pub(crate) fn add_block(&mut self) -> BlockId { + self.blocks.add_block( + self.values.count().0, + BlockInfo { + param: None, + jump_targets: SmallVec::new(), + target_arg: None, + }, + ) + } + + pub(crate) fn add_block_with_param(&mut self, param_type: TypeId) -> (BlockId, ValueId) { + let param_id = self.values.add_node( + ValueInfo { + kind: ValueKind::BlockParam, + result_type: param_type, + }, + &[], + ); + let block_id = self.blocks.add_block( + self.values.count().0, + BlockInfo { + param: Some(param_id), + jump_targets: SmallVec::new(), + target_arg: None, + }, + ); + (block_id, param_id) + } + + pub(crate) fn set_jump_targets( + &mut self, + block: BlockId, + target_arg: Option, + jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, + ) { + let info = self.blocks.block_info_mut(block); + info.target_arg = target_arg; + info.jump_targets = jump_targets; + } + + pub(crate) fn values(&self) -> &OpGraph { + &self.values + } + + pub(crate) fn blocks(&self) -> &Blocks { + &self.blocks + } + + pub(crate) fn build(self, entry_block: BlockId) -> Graph { + debug_assert!(entry_block < self.blocks.block_count()); + let rev_sccs = strongly_connected(self.blocks.block_count(), |block| { + self.blocks + .block_info(block) + .jump_targets + .iter() + .filter_map(|&jump_target| match jump_target { + JumpTarget::Ret => None, + JumpTarget::Block(target) => Some(target), + }) + }); + Graph { + values: self.values, + blocks: self.blocks, + entry_block, + rev_sccs, + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct Graph { + values: OpGraph, + blocks: Blocks, + entry_block: BlockId, + + // Invariant: `rev_sccs` must be stored in *reverse* topological order. If an SCC 'A' can jump + // to an SCC 'B', then 'A' must appear *after* 'B' in `rev_sccs`. + // + // We don't store the SCCs in topological order because control flow graph edges point from + // *source block* to *target block*, so running Tarjan's algorithm on the control flow graph + // gives us a reverse topological sort rather than a topological sort. + rev_sccs: FlatSlices, +} + +impl Graph { + pub(crate) fn values(&self) -> &OpGraph { + &self.values + } + + pub(crate) fn blocks(&self) -> &Blocks { + &self.blocks + } + + pub(crate) fn entry_block(&self) -> BlockId { + self.entry_block + } + + pub(crate) fn rev_sccs(&self) -> &FlatSlices { + &self.rev_sccs + } + + /// Iterate over sccs in topological order. + /// + /// IF an SCC 'A' can jump to an SCC 'B', then 'A' is guaranteed to appear *before* 'B' in the + /// returned iterator. + pub(crate) fn iter_sccs(&self) -> impl Iterator> + '_ { + self.rev_sccs + .count() + .iter() + .rev() + .map(move |scc_id| self.rev_sccs.get(scc_id)) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct FuncDef { + pub(crate) graph: Graph, +} + +#[derive(Clone, Debug)] +pub(crate) struct ConstDef { + pub(crate) graph: Graph, +} diff --git a/vendor/morphic_lib/src/lib.rs b/vendor/morphic_lib/src/lib.rs index 89da624e4a..a1470770dc 100644 --- a/vendor/morphic_lib/src/lib.rs +++ b/vendor/morphic_lib/src/lib.rs @@ -1,10 +1,15 @@ #![allow(dead_code)] +#![allow(clippy::too_many_arguments)] #[macro_use] mod util; mod api; mod bindings; +mod ir; +mod name_cache; +mod preprocess; mod render_api_ir; +mod type_cache; pub use api::*; diff --git a/vendor/morphic_lib/src/name_cache.rs b/vendor/morphic_lib/src/name_cache.rs new file mode 100644 index 0000000000..03e45fb821 --- /dev/null +++ b/vendor/morphic_lib/src/name_cache.rs @@ -0,0 +1,26 @@ +use crate::api; +use crate::util::id_bi_map::IdBiMap; + +id_type! { + pub NamedTypeId(u32); +} + +id_type! { + pub FuncId(u32); +} + +id_type! { + pub ConstId(u32); +} + +id_type! { + pub EntryPointId(u32); +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct NameCache { + pub(crate) named_types: IdBiMap, + pub(crate) funcs: IdBiMap, + pub(crate) consts: IdBiMap, + pub(crate) entry_points: IdBiMap, +} diff --git a/vendor/morphic_lib/src/preprocess.rs b/vendor/morphic_lib/src/preprocess.rs new file mode 100644 index 0000000000..161d61ca9f --- /dev/null +++ b/vendor/morphic_lib/src/preprocess.rs @@ -0,0 +1,1204 @@ +//! Before we perform any interprocedural analysis, we run a preprocessing pass to convert the "API +//! IR" (the IR constructed by the input API) to the "analysis IR" (the IR used for all subsequent +//! analysis). +//! +//! We cannot perform this preprocessing step eagerly while the API IR is being constructed, because +//! some parts of the preprocessing (for example, typechecking) rely on nonlocal information (such +//! as functions' type signatures) which is not available until the entire input program has been +//! constructed. +//! +//! This pass currently implements the following functionality: +//! - Scope checking for values +//! - Typechecking + +use smallvec::{smallvec, SmallVec}; + +use crate::api; +use crate::ir; +use crate::ir::JumpTarget; +use crate::name_cache::{ConstId, EntryPointId, FuncId, NameCache, NamedTypeId}; +use crate::type_cache::{TypeCache, TypeData, TypeId}; +use crate::util::id_vec::IdVec; +use crate::util::op_graph::OpGraph; +use crate::util::replace_none::replace_none; + +#[derive(Clone, thiserror::Error, Debug)] +pub(crate) enum ErrorKind { + #[error("could not find named type in module {0:?} with name {1:?}")] + NamedTypeNotFound(api::ModNameBuf, api::TypeNameBuf), + #[error("could not find func in module {0:?} with name {1:?}")] + FuncNotFound(api::ModNameBuf, api::FuncNameBuf), + #[error("could not find const in module {0:?} with name {1:?}")] + ConstNotFound(api::ModNameBuf, api::ConstNameBuf), + #[error("value {0:?} is not in scope")] + ValueNotInScope(api::ValueId), + #[error("continuation {0:?} is not in scope")] + ContinuationNotInScope(api::ContinuationId), + #[error("expected type '{expected}', found type '{actual}'")] + TypeMismatch { expected: String, actual: String }, + #[error("expected bag type, found type '{0}'")] + ExpectedBagType(String), + #[error("expected tuple type, found type '{0}'")] + ExpectedTupleType(String), + #[error("expected union type, found type '{0}'")] + ExpectedUnionType(String), + #[error("expected named type, foudn type '{0}'")] + ExpectedNamedType(String), + #[error("tuple field index {0} out of range")] + TupleFieldOutOfRange(u32), + #[error("union variant index {0} out of range")] + UnionVariantOutOfRange(u32), + #[error("entry point does not have type '() -> ()'")] + EntryPointDisallowedSig, +} + +/// For use in error locations +#[derive(Clone, Debug)] +enum DefName { + Type(api::TypeNameBuf), + Func(api::FuncNameBuf), + Const(api::ConstNameBuf), + EntryPoint(api::EntryPointNameBuf), +} + +#[derive(Clone, Debug)] +enum BindingLocation { + Type(api::TypeId), + Value(api::ValueId), + Continuation(api::ContinuationId), +} + +#[derive(Clone, Debug)] +pub(crate) struct Error { + kind: ErrorKind, + mod_: Option, + def: Option, + binding: Option, +} + +impl Error { + fn annotate_mod_def>(err: E, mod_: api::ModNameBuf, def: DefName) -> Self { + let mut err = err.into(); + if err.mod_.is_none() { + err.mod_ = Some(mod_); + } + if err.def.is_none() { + err.def = Some(def); + } + err + } + + fn annotate_type_def>( + nc: &NameCache, + def_id: NamedTypeId, + ) -> impl FnOnce(E) -> Self + '_ { + move |err| { + let (mod_, name) = &nc.named_types[def_id]; + Error::annotate_mod_def(err, mod_.clone(), DefName::Type(name.clone())) + } + } + + fn annotate_func_def>( + nc: &NameCache, + def_id: FuncId, + ) -> impl FnOnce(E) -> Self + '_ { + move |err| { + let (mod_, name) = &nc.funcs[def_id]; + Error::annotate_mod_def(err, mod_.clone(), DefName::Func(name.clone())) + } + } + + fn annotate_const_def>( + nc: &NameCache, + def_id: ConstId, + ) -> impl FnOnce(E) -> Self + '_ { + move |err| { + let (mod_, name) = &nc.consts[def_id]; + Error::annotate_mod_def(err, mod_.clone(), DefName::Const(name.clone())) + } + } + + fn annotate_entry_point>( + nc: &NameCache, + def_id: EntryPointId, + ) -> impl FnOnce(E) -> Self + '_ { + move |err| { + let mut err = err.into(); + if err.def.is_none() { + err.def = Some(DefName::EntryPoint(nc.entry_points[def_id].clone())); + } + err + } + } + + fn annotate_binding>(binding: BindingLocation) -> impl FnOnce(E) -> Self { + move |err| { + let mut err = err.into(); + if err.binding.is_none() { + err.binding = Some(binding); + } + err + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut first = true; + let mut loc_prefix = |f: &mut std::fmt::Formatter| -> std::fmt::Result { + if first { + write!(f, "error in ")?; + } else { + write!(f, ", ")?; + } + first = false; + Ok(()) + }; + + if let Some(mod_) = &self.mod_ { + loc_prefix(f)?; + write!(f, "module {:?}", mod_)?; + } + + if let Some(def) = &self.def { + loc_prefix(f)?; + match def { + DefName::Type(name) => { + write!(f, "named type definition {:?}", name)?; + } + DefName::Func(name) => { + write!(f, "function definition {:?}", name)?; + } + DefName::Const(name) => { + write!(f, "constant definition {:?}", name)?; + } + DefName::EntryPoint(name) => { + write!(f, "entry point definition {:?}", name)?; + } + } + } + + if let Some(binding) = &self.binding { + loc_prefix(f)?; + match binding { + BindingLocation::Type(id) => { + write!(f, "definition of type binding {:?}", id)?; + } + BindingLocation::Value(id) => { + write!(f, "definition of value binding {:?}", id)?; + } + BindingLocation::Continuation(id) => { + write!(f, "definition of continuation binding {:?}", id)?; + } + } + } + + if !first { + write!(f, ": ")?; + } + + write!(f, "{}", self.kind) + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Error { + kind, + mod_: None, + def: None, + binding: None, + } + } +} + +fn named_type_id( + nc: &NameCache, + mod_name: &api::ModNameBuf, + type_name: &api::TypeNameBuf, +) -> Result { + nc.named_types + .get_by_val(&(mod_name.clone(), type_name.clone())) + .ok_or_else(|| ErrorKind::NamedTypeNotFound(mod_name.clone(), type_name.clone()).into()) +} + +pub(crate) fn resolve_types( + nc: &NameCache, + tc: &mut TypeCache, + types_: &OpGraph, +) -> Result, Error> { + let mut result = IdVec::with_capacity(types_.len()); + for api_id in types_.count().iter() { + let node = types_.node(api_id); + let inputs: SmallVec<_> = node.inputs.iter().map(|input| result[input]).collect(); + let type_data = match node.op { + api::TypeOp::Named { named_mod, named } => { + let named_id = named_type_id(nc, named_mod, named) + .map_err(Error::annotate_binding(BindingLocation::Type(api_id)))?; + debug_assert_eq!(inputs.len(), 0); + TypeData::Named { named: named_id } + } + api::TypeOp::Tuple => TypeData::Tuple { fields: inputs }, + api::TypeOp::Union => TypeData::Union { variants: inputs }, + api::TypeOp::HeapCell => { + debug_assert_eq!(inputs.len(), 0); + TypeData::HeapCell + } + api::TypeOp::Bag => { + debug_assert_eq!(inputs.len(), 1); + TypeData::Bag { item: inputs[0] } + } + }; + let type_id = tc.types.get_or_insert(type_data); + let pushed_id = result.push(type_id); + debug_assert_eq!(pushed_id, api_id); + } + debug_assert_eq!(result.count(), types_.count()); + Ok(result) +} + +#[derive(Clone, Debug)] +struct FuncSig { + arg_type: TypeId, + ret_type: TypeId, +} + +pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { + let mut nc = NameCache::default(); + let mut tc = TypeCache::default(); + + let mut named_types = IdVec::::new(); + let mut funcs = IdVec::::new(); + let mut consts = IdVec::::new(); + + for (mod_name, mod_) in &program.mods { + for (type_name, type_def) in &mod_.type_defs { + let nc_id = nc + .named_types + .insert((mod_name.clone(), type_name.clone())) + .expect("Builder API should check for duplicate names"); + let pushed_id = named_types.push(type_def); + debug_assert_eq!(nc_id, pushed_id); + } + for (func_name, func_def) in &mod_.func_defs { + let nc_id = nc + .funcs + .insert((mod_name.clone(), func_name.clone())) + .expect("Builder API should check for duplicate names"); + let pushed_id = funcs.push(func_def); + debug_assert_eq!(nc_id, pushed_id); + } + for (const_name, const_def) in &mod_.const_defs { + let nc_id = nc + .consts + .insert((mod_name.clone(), const_name.clone())) + .expect("Builder API should check for duplicate names"); + let pushed_id = consts.push(const_def); + debug_assert_eq!(nc_id, pushed_id); + } + } + + // TODO: We should ideally perform fewer passes over the program here. + + let typedef_body_types = named_types.try_map(|named_id, type_def| { + resolve_types(&nc, &mut tc, &type_def.builder.types) + .map_err(Error::annotate_type_def(&nc, named_id)) + })?; + + let typedef_contents = + named_types.map(|named_type_id, type_def| typedef_body_types[named_type_id][type_def.root]); + + let func_body_types = funcs.try_map(|func_id, func_def| { + resolve_types( + &nc, + &mut tc, + &func_def.builder.expr_builder.type_builder.types, + ) + .map_err(Error::annotate_func_def(&nc, func_id)) + })?; + + let func_sigs = funcs.map(|func_id, func_def| { + let body_types = &func_body_types[func_id]; + FuncSig { + arg_type: body_types[func_def.arg_type], + ret_type: body_types[func_def.ret_type], + } + }); + + let const_body_types = consts.try_map(|const_id, const_def| { + resolve_types( + &nc, + &mut tc, + &const_def.builder.expr_builder.type_builder.types, + ) + .map_err(Error::annotate_const_def(&nc, const_id)) + })?; + + let const_sigs = consts.map(|const_id, const_def| { + let body_types = &const_body_types[const_id]; + body_types[&const_def.type_] + }); + + let ctx = Context { + nc: &nc, + typedef_contents: &typedef_contents, + func_sigs: &func_sigs, + const_sigs: &const_sigs, + }; + + for (func_id, func_def) in &funcs { + preprocess_func_def(&mut tc, ctx, func_def, &func_body_types[func_id]) + .map_err(Error::annotate_func_def(&nc, func_id))?; + } + + for (const_id, const_def) in &consts { + preprocess_const_def(&mut tc, ctx, const_def, &const_body_types[const_id]) + .map_err(Error::annotate_const_def(&nc, const_id))?; + } + + let mut entry_points = IdVec::::new(); + for (entry_point_name, (mod_, func)) in &program.entry_points { + let nc_id = nc + .entry_points + .insert(entry_point_name.clone()) + .expect("builder API should check for duplicate names"); + // TODO: Avoid the clones here + let func_id = nc + .funcs + .get_by_val(&(mod_.clone(), func.clone())) + .ok_or_else(|| ErrorKind::FuncNotFound(mod_.clone(), func.clone())) + .map_err(Error::annotate_entry_point(&nc, nc_id))?; + let func_sig = &func_sigs[func_id]; + let unit_type = tc.types.get_or_insert(TypeData::Tuple { + fields: smallvec![], + }); + if func_sig.arg_type != unit_type || func_sig.ret_type != unit_type { + return Err(Error::annotate_entry_point(&nc, nc_id)( + ErrorKind::EntryPointDisallowedSig, + )); + } + let pushed_id = entry_points.push(func_id); + debug_assert_eq!(nc_id, pushed_id); + } + + Ok(()) +} + +#[derive(Clone, Copy, Debug)] +struct Context<'a> { + nc: &'a NameCache, + typedef_contents: &'a IdVec, + func_sigs: &'a IdVec, + const_sigs: &'a IdVec, +} + +#[derive(Clone, Copy, Debug)] +struct ValueBinding { + id: ir::ValueId, + type_: TypeId, +} + +#[derive(Clone, Copy, Debug)] +struct ContinuationBinding { + entry_block: ir::BlockId, + arg_type: TypeId, +} + +// TODO: Consolidate this logic with render_api_ir.rs +fn render_byte_string(target: &mut String, bytes: &[u8]) { + target.push('"'); + for &byte in bytes { + use std::fmt::Write; + write!(target, "{}", std::ascii::escape_default(byte)).unwrap(); + } + target.push('"'); +} + +// TODO: Consolidate this logic with render_api_ir.rs +fn render_type_rec(nc: &NameCache, tc: &TypeCache, target: &mut String, type_: TypeId) { + match &tc.types[type_] { + TypeData::Named { named } => { + let (mod_name, type_name) = &nc.named_types[named]; + render_byte_string(target, &mod_name.0); + target.push_str("::"); + render_byte_string(target, &type_name.0); + } + + TypeData::Tuple { fields } => { + target.push('('); + let mut first = true; + for &field in fields { + if !first { + target.push_str(", "); + } + first = false; + render_type_rec(nc, tc, target, field); + } + if fields.len() == 1 { + target.push(','); + } + target.push(')'); + } + + TypeData::Union { variants } => { + if variants.is_empty() { + // Special case to avoid extra space inside braces + target.push_str("union { }"); + return; + } + target.push_str("union { "); + let mut first = true; + for &variant in variants { + if !first { + target.push_str(", "); + } + first = false; + render_type_rec(nc, tc, target, variant); + } + target.push_str(" }"); + } + + TypeData::HeapCell => { + target.push_str("heap_cell"); + } + + TypeData::Bag { item } => { + target.push_str("bag<"); + render_type_rec(nc, tc, target, *item); + target.push('>'); + } + } +} + +fn render_type(nc: &NameCache, tc: &TypeCache, type_: TypeId) -> String { + let mut target = String::new(); + render_type_rec(nc, tc, &mut target, type_); + target +} + +fn type_error( + nc: &NameCache, + tc: &TypeCache, + expected: TypeId, + actual: TypeId, +) -> Result { + debug_assert_ne!(expected, actual); + Err(ErrorKind::TypeMismatch { + expected: render_type(nc, tc, expected), + actual: render_type(nc, tc, actual), + } + .into()) +} + +fn check_type( + nc: &NameCache, + tc: &TypeCache, + expected: TypeId, + actual: TypeId, +) -> Result<(), Error> { + if expected != actual { + type_error(nc, tc, expected, actual) + } else { + Ok(()) + } +} + +fn try_get_bag_item_type( + nc: &NameCache, + tc: &TypeCache, + bag_type: TypeId, +) -> Result { + if let TypeData::Bag { item } = tc.types[bag_type] { + Ok(item) + } else { + Err(ErrorKind::ExpectedBagType(render_type(nc, tc, bag_type)).into()) + } +} + +fn try_get_tuple_field_types<'a>( + nc: &NameCache, + tc: &'a TypeCache, + tuple_type: TypeId, +) -> Result<&'a [TypeId], Error> { + if let TypeData::Tuple { fields } = &tc.types[tuple_type] { + Ok(fields) + } else { + Err(ErrorKind::ExpectedTupleType(render_type(nc, tc, tuple_type)).into()) + } +} + +fn try_get_union_variant_types<'a>( + nc: &NameCache, + tc: &'a TypeCache, + union_type: TypeId, +) -> Result<&'a [TypeId], Error> { + if let TypeData::Union { variants } = &tc.types[union_type] { + Ok(variants) + } else { + Err(ErrorKind::ExpectedUnionType(render_type(nc, tc, union_type)).into()) + } +} + +fn try_get_named_type_id( + nc: &NameCache, + tc: &TypeCache, + named_type: TypeId, +) -> Result { + if let TypeData::Named { named } = tc.types[named_type] { + Ok(named) + } else { + Err(ErrorKind::ExpectedNamedType(render_type(nc, tc, named_type)).into()) + } +} + +fn get_value( + values_in_scope: &IdVec>, + api_value: api::ValueId, +) -> Result { + values_in_scope[api_value].ok_or_else(|| ErrorKind::ValueNotInScope(api_value).into()) +} + +fn preprocess_block_expr( + tc: &mut TypeCache, + ctx: Context, + api_builder: &api::ExprBuilder, + body_types: &IdVec, + graph_builder: &mut ir::GraphBuilder, + values_in_scope: &mut IdVec>, + continuations_in_scope: &mut IdVec>, + mut block: ir::BlockId, + block_expr: api::BlockExpr, +) -> Result<(ir::BlockId, ir::ValueId), Error> { + let api::BlockExpr(api_block, api_ret_val) = block_expr; + + let mut join_info = None; + + let mut api_value_ids = api_builder.blocks.block_values(api_block).peekable(); + loop { + let mut continuation_group = SmallVec::<[_; 32]>::new(); + loop { + if let Some(api_value_id) = api_value_ids.peek() { + if let api::Op::DeclareContinuation { continuation } = + api_builder.vals.node(api_value_id).op + { + let info = api_builder.continuations[continuation]; + let arg_type = body_types[info.arg_type]; + let (entry_block, arg) = graph_builder.add_block_with_param(arg_type); + replace_none( + &mut continuations_in_scope[continuation], + ContinuationBinding { + entry_block, + arg_type, + }, + ) + .unwrap(); + continuation_group.push((continuation, arg)); + api_value_ids.next(); + continue; + } + } + break; + } + + for (continuation, arg) in continuation_group { + let ContinuationBinding { + entry_block, + arg_type: _, + } = continuations_in_scope[continuation].unwrap(); + + let api::ContinuationInfo { + arg_type: _, + ret_type: api_ret_type, + arg: api_arg, + body: api_body, + } = api_builder.continuations[continuation]; + + replace_none(&mut values_in_scope[api_arg], arg).unwrap(); + let (continuation_final_block, continuation_ret_val) = preprocess_block_expr( + tc, + ctx, + api_builder, + body_types, + graph_builder, + values_in_scope, + continuations_in_scope, + entry_block, + api_body + .expect("builder API should have checked that all continuations are defined"), + ) + .map_err(Error::annotate_binding(BindingLocation::Continuation( + *continuation, + )))?; + values_in_scope[api_arg] = None; + + let ret_type = body_types[api_ret_type]; + + check_type( + ctx.nc, + tc, + ret_type, + graph_builder + .values() + .node(continuation_ret_val) + .op + .result_type, + ) + .map_err(Error::annotate_binding(BindingLocation::Continuation( + *continuation, + )))?; + + let (join_block, join_val_type, _) = *join_info.get_or_insert_with(|| { + let (join_block, join_val) = graph_builder.add_block_with_param(ret_type); + (join_block, ret_type, join_val) + }); + + check_type(ctx.nc, tc, join_val_type, ret_type).map_err(Error::annotate_binding( + BindingLocation::Continuation(*continuation), + ))?; + + graph_builder.set_jump_targets( + continuation_final_block, + Some(continuation_ret_val), + smallvec![ir::JumpTarget::Block(join_block)], + ); + } + + match api_value_ids.next() { + None => break, + Some(api_value_id) => { + let api_node = api_builder.vals.node(api_value_id); + debug_assert!(!matches!(&api_node.op, api::Op::DeclareContinuation { .. })); + let (new_block, value) = preprocess_op( + tc, + ctx, + api_builder, + body_types, + graph_builder, + values_in_scope, + continuations_in_scope, + block, + &api_node.op, + &api_node.inputs, + ) + .map_err(Error::annotate_binding(BindingLocation::Value( + api_value_id, + )))?; + block = new_block; + replace_none(&mut values_in_scope[api_value_id], value).unwrap(); + } + } + } + + let (final_block, final_value) = match join_info { + None => (block, get_value(values_in_scope, api_ret_val)?), + Some((join_block, join_val_type, join_val)) => { + let ret_val = get_value(values_in_scope, api_ret_val)?; + check_type( + ctx.nc, + tc, + join_val_type, + graph_builder.values().node(ret_val).op.result_type, + )?; + graph_builder.set_jump_targets( + block, + Some(ret_val), + smallvec![ir::JumpTarget::Block(join_block)], + ); + (join_block, join_val) + } + }; + + for api_value_id in api_builder.blocks.block_values(api_block) { + match &api_builder.vals.node(api_value_id).op { + api::Op::DeclareContinuation { continuation } => { + debug_assert!(values_in_scope[api_value_id].is_none()); + debug_assert!(continuations_in_scope[continuation].is_some()); + continuations_in_scope[continuation] = None; + } + + _ => { + debug_assert!(values_in_scope[api_value_id].is_some()); + values_in_scope[api_value_id] = None; + } + } + } + + Ok((final_block, final_value)) +} + +fn preprocess_op( + tc: &mut TypeCache, + ctx: Context, + api_builder: &api::ExprBuilder, + body_types: &IdVec, + graph_builder: &mut ir::GraphBuilder, + values_in_scope: &mut IdVec>, + continuations_in_scope: &mut IdVec>, + block: ir::BlockId, + api_op: &api::Op, + api_inputs: &[api::ValueId], +) -> Result<(ir::BlockId, ir::ValueId), Error> { + let mut inputs = SmallVec::<[_; 32]>::with_capacity(api_inputs.len()); + let mut input_types = SmallVec::<[_; 32]>::with_capacity(api_inputs.len()); + for &api_input in api_inputs { + let id = get_value(values_in_scope, api_input)?; + inputs.push(id); + input_types.push(graph_builder.values().node(id).op.result_type); + } + + match api_op { + api::Op::Arg | api::Op::ContinuationArg | api::Op::DeclareContinuation { .. } => { + unreachable!( + "op {:?} should never appear as a value binding in a block", + api_op, + ) + } + + api::Op::Jump { + continuation, + unreachable_result_type, + } => { + debug_assert_eq!(inputs.len(), 1); + + let cont_binding = continuations_in_scope[continuation] + .ok_or_else(|| ErrorKind::ContinuationNotInScope(*continuation))?; + + check_type(ctx.nc, tc, cont_binding.arg_type, input_types[0])?; + + graph_builder.set_jump_targets( + block, + Some(inputs[0]), + smallvec![ir::JumpTarget::Block(cont_binding.entry_block)], + ); + + let (unreachable_block, unreachable_result) = + graph_builder.add_block_with_param(body_types[unreachable_result_type]); + + Ok((unreachable_block, unreachable_result)) + } + + api::Op::UnknownWith { result_type } => { + let value = graph_builder.add_op( + block, + ir::OpKind::UnknownWith, + &inputs, + body_types[result_type], + ); + Ok((block, value)) + } + + api::Op::Call { + callee_spec_var, + callee_mod, + callee, + } => { + debug_assert_eq!(inputs.len(), 1); + + // TODO: Avoid the clones here + let callee_id = ctx + .nc + .funcs + .get_by_val(&(callee_mod.clone(), callee.clone())) + .ok_or_else(|| ErrorKind::FuncNotFound(callee_mod.clone(), callee.clone()))?; + + let callee_sig = &ctx.func_sigs[callee_id]; + + check_type(ctx.nc, tc, callee_sig.arg_type, input_types[0])?; + + let value = graph_builder.add_op( + block, + ir::OpKind::Call { + callee_spec_var: *callee_spec_var, + callee: callee_id, + }, + &inputs, + callee_sig.ret_type, + ); + Ok((block, value)) + } + + api::Op::ConstRef { const_mod, const_ } => { + debug_assert_eq!(inputs.len(), 0); + + let const_id = ctx + .nc + .consts + .get_by_val(&(const_mod.clone(), const_.clone())) + .ok_or_else(|| ErrorKind::ConstNotFound(const_mod.clone(), const_.clone()))?; + + let const_type = ctx.const_sigs[const_id]; + + let value = graph_builder.add_op( + block, + ir::OpKind::ConstRef { const_: const_id }, + &[], + const_type, + ); + Ok((block, value)) + } + + api::Op::Choice { cases } => { + debug_assert_eq!(inputs.len(), 0); + debug_assert_ne!(cases.len(), 0); + + let case_results: SmallVec<[_; 32]> = cases + .iter() + .map(|&case| { + let case_entry_block = graph_builder.add_block(); + let (case_final_block, case_value) = preprocess_block_expr( + tc, + ctx, + api_builder, + body_types, + graph_builder, + values_in_scope, + continuations_in_scope, + case_entry_block, + case, + )?; + Ok((case_entry_block, case_final_block, case_value)) + }) + .collect::>()?; + + graph_builder.set_jump_targets( + block, + None, + case_results + .iter() + .map(|&(case_entry_block, _, _)| ir::JumpTarget::Block(case_entry_block)) + .collect(), + ); + + let mut result_type = None; + for &(_, _, case_value) in &case_results { + let case_value_type = graph_builder.values().node(case_value).op.result_type; + if let Some(prev_result_type) = result_type { + check_type(ctx.nc, tc, prev_result_type, case_value_type)?; + } else { + result_type = Some(case_value_type); + } + } + let result_type = result_type.expect("case_results is nonempty"); + + let (successor_block, choice_value) = graph_builder.add_block_with_param(result_type); + + for &(_, case_final_block, case_value) in &case_results { + graph_builder.set_jump_targets( + case_final_block, + Some(case_value), + smallvec![JumpTarget::Block(successor_block)], + ); + } + + Ok((successor_block, choice_value)) + } + + api::Op::SubBlock { sub_block } => { + debug_assert_eq!(inputs.len(), 0); + preprocess_block_expr( + tc, + ctx, + api_builder, + body_types, + graph_builder, + values_in_scope, + continuations_in_scope, + block, + *sub_block, + ) + } + + api::Op::Terminate { + unreachable_result_type, + } => { + debug_assert_eq!(inputs.len(), 0); + graph_builder.set_jump_targets(block, None, smallvec![]); + let (unreachable_block, unreachable_value) = + graph_builder.add_block_with_param(body_types[unreachable_result_type]); + Ok((unreachable_block, unreachable_value)) + } + + api::Op::NewHeapCell => { + debug_assert_eq!(inputs.len(), 0); + let value = graph_builder.add_op( + block, + ir::OpKind::NewHeapCell, + &[], + tc.types.get_or_insert(TypeData::HeapCell), + ); + Ok((block, value)) + } + + api::Op::Touch => { + debug_assert_eq!(inputs.len(), 1); + let heap_cell_type = tc.types.get_or_insert(TypeData::HeapCell); + check_type(ctx.nc, tc, heap_cell_type, input_types[0])?; + let value = graph_builder.add_op( + block, + ir::OpKind::RecursiveTouch, + &inputs, + tc.types.get_or_insert(TypeData::Tuple { + fields: smallvec![], + }), + ); + Ok((block, value)) + } + + api::Op::RecursiveTouch => { + debug_assert_eq!(inputs.len(), 1); + let value = graph_builder.add_op( + block, + ir::OpKind::RecursiveTouch, + &inputs, + tc.types.get_or_insert(TypeData::Tuple { + fields: smallvec![], + }), + ); + Ok((block, value)) + } + + api::Op::UpdateWriteOnly { update_mode_var } => { + debug_assert_eq!(inputs.len(), 1); + let heap_cell_type = tc.types.get_or_insert(TypeData::HeapCell); + check_type(ctx.nc, tc, heap_cell_type, input_types[0])?; + let value = graph_builder.add_op( + block, + ir::OpKind::UpdateWriteOnly { + update_mode_var: *update_mode_var, + }, + &inputs, + tc.types.get_or_insert(TypeData::Tuple { + fields: smallvec![], + }), + ); + Ok((block, value)) + } + + api::Op::EmptyBag { item_type } => { + debug_assert_eq!(inputs.len(), 0); + let value = graph_builder.add_op( + block, + ir::OpKind::EmptyBag, + &[], + tc.types.get_or_insert(TypeData::Bag { + item: body_types[item_type], + }), + ); + Ok((block, value)) + } + + api::Op::BagInsert => { + debug_assert_eq!(inputs.len(), 2); + let bag_type = input_types[0]; + let expected_item_type = try_get_bag_item_type(ctx.nc, tc, bag_type)?; + check_type(ctx.nc, tc, expected_item_type, input_types[1])?; + let value = graph_builder.add_op(block, ir::OpKind::BagInsert, &inputs, bag_type); + Ok((block, value)) + } + + api::Op::BagGet => { + debug_assert_eq!(inputs.len(), 1); + let item_type = try_get_bag_item_type(ctx.nc, tc, input_types[0])?; + let value = graph_builder.add_op(block, ir::OpKind::BagGet, &inputs, item_type); + Ok((block, value)) + } + + api::Op::BagRemove => { + debug_assert_eq!(inputs.len(), 1); + let bag_type = input_types[0]; + let item_type = try_get_bag_item_type(ctx.nc, tc, bag_type)?; + let value = graph_builder.add_op( + block, + ir::OpKind::BagRemove, + &inputs, + tc.types.get_or_insert(TypeData::Tuple { + fields: smallvec![bag_type, item_type], + }), + ); + Ok((block, value)) + } + + api::Op::MakeTuple => { + let tuple_type = tc.types.get_or_insert(TypeData::Tuple { + fields: SmallVec::from_slice(&input_types), + }); + let value = graph_builder.add_op(block, ir::OpKind::MakeTuple, &inputs, tuple_type); + Ok((block, value)) + } + + api::Op::GetTupleField { field_idx } => { + debug_assert_eq!(inputs.len(), 1); + let tuple_type = input_types[0]; + let field_types = try_get_tuple_field_types(ctx.nc, tc, tuple_type)?; + let field_type = *field_types + .get(*field_idx as usize) + .ok_or_else(|| ErrorKind::TupleFieldOutOfRange(*field_idx))?; + let value = graph_builder.add_op( + block, + ir::OpKind::GetTupleField { + field_idx: *field_idx, + }, + &inputs, + field_type, + ); + Ok((block, value)) + } + + api::Op::MakeUnion { + variant_types, + variant_idx, + } => { + debug_assert_eq!(inputs.len(), 1); + let tc_variant_types: SmallVec<_> = variant_types + .iter() + .map(|api_type_id| body_types[api_type_id]) + .collect(); + let this_variant_type = *tc_variant_types + .get(*variant_idx as usize) + .ok_or_else(|| ErrorKind::UnionVariantOutOfRange(*variant_idx))?; + check_type(ctx.nc, tc, this_variant_type, input_types[0])?; + let union_type = tc.types.get_or_insert(TypeData::Union { + variants: tc_variant_types, + }); + let value = graph_builder.add_op( + block, + ir::OpKind::MakeUnion { + variant_idx: *variant_idx, + }, + &inputs, + union_type, + ); + Ok((block, value)) + } + + api::Op::UnwrapUnion { variant_idx } => { + debug_assert_eq!(inputs.len(), 1); + let variant_types = try_get_union_variant_types(ctx.nc, tc, input_types[0])?; + let this_variant_type = *variant_types + .get(*variant_idx as usize) + .ok_or_else(|| ErrorKind::UnionVariantOutOfRange(*variant_idx))?; + let value = graph_builder.add_op( + block, + ir::OpKind::UnwrapUnion { + variant_idx: *variant_idx, + }, + &inputs, + this_variant_type, + ); + Ok((block, value)) + } + + api::Op::MakeNamed { named_mod, named } => { + debug_assert_eq!(inputs.len(), 1); + // TODO: Avoid the clones here + let named_id = ctx + .nc + .named_types + .get_by_val(&(named_mod.clone(), named.clone())) + .ok_or_else(|| ErrorKind::NamedTypeNotFound(named_mod.clone(), named.clone()))?; + let content_type = ctx.typedef_contents[named_id]; + check_type(ctx.nc, tc, content_type, input_types[0])?; + let value = graph_builder.add_op( + block, + ir::OpKind::MakeNamed, + &inputs, + tc.types.get_or_insert(TypeData::Named { named: named_id }), + ); + Ok((block, value)) + } + + api::Op::UnwrapNamed { named_mod, named } => { + debug_assert_eq!(inputs.len(), 1); + let named_id = try_get_named_type_id(ctx.nc, tc, input_types[0])?; + let expected_named_id = ctx + .nc + .named_types + .get_by_val(&(named_mod.clone(), named.clone())) + .ok_or_else(|| ErrorKind::NamedTypeNotFound(named_mod.clone(), named.clone()))?; + if named_id != expected_named_id { + let expected = tc.types.get_or_insert(TypeData::Named { + named: expected_named_id, + }); + type_error(ctx.nc, tc, expected, input_types[0])?; + } + let value = graph_builder.add_op( + block, + ir::OpKind::UnwrapNamed, + &inputs, + ctx.typedef_contents[named_id], + ); + Ok((block, value)) + } + } +} + +fn preprocess_func_def( + tc: &mut TypeCache, + ctx: Context, + func_def: &api::FuncDef, + body_types: &IdVec, +) -> Result { + let api_builder = &func_def.builder.expr_builder; + let mut graph_builder = ir::GraphBuilder::new(); + let mut values_in_scope = IdVec::filled_with(api_builder.vals.count(), || None); + let mut continuations_in_scope = IdVec::filled_with(api_builder.continuations.count(), || None); + let (entry_block, arg_val) = graph_builder.add_block_with_param(body_types[func_def.arg_type]); + values_in_scope[func_def.builder.argument] = Some(arg_val); + let (final_block, ret_val) = preprocess_block_expr( + tc, + ctx, + &api_builder, + body_types, + &mut graph_builder, + &mut values_in_scope, + &mut continuations_in_scope, + entry_block, + func_def.root, + )?; + check_type( + ctx.nc, + tc, + body_types[func_def.ret_type], + graph_builder.values().node(ret_val).op.result_type, + )?; + graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); + Ok(ir::FuncDef { + graph: graph_builder.build(entry_block), + }) +} + +fn preprocess_const_def( + tc: &mut TypeCache, + ctx: Context, + const_def: &api::ConstDef, + body_types: &IdVec, +) -> Result { + let api_builder = &const_def.builder.expr_builder; + let mut graph_builder = ir::GraphBuilder::new(); + let mut values_in_scope = IdVec::filled_with(api_builder.vals.count(), || None); + let mut continuations_in_scope = IdVec::filled_with(api_builder.continuations.count(), || None); + let entry_block = graph_builder.add_block(); + let (final_block, ret_val) = preprocess_block_expr( + tc, + ctx, + &api_builder, + body_types, + &mut graph_builder, + &mut values_in_scope, + &mut continuations_in_scope, + entry_block, + const_def.root, + )?; + check_type( + ctx.nc, + tc, + body_types[const_def.type_], + graph_builder.values().node(ret_val).op.result_type, + )?; + graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); + Ok(ir::ConstDef { + graph: graph_builder.build(entry_block), + }) +} diff --git a/vendor/morphic_lib/src/type_cache.rs b/vendor/morphic_lib/src/type_cache.rs new file mode 100644 index 0000000000..beb16b182b --- /dev/null +++ b/vendor/morphic_lib/src/type_cache.rs @@ -0,0 +1,22 @@ +use smallvec::SmallVec; + +use crate::name_cache::NamedTypeId; +use crate::util::id_bi_map::IdBiMap; + +id_type! { + pub TypeId(u32); +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum TypeData { + Named { named: NamedTypeId }, + Tuple { fields: SmallVec<[TypeId; 10]> }, + Union { variants: SmallVec<[TypeId; 10]> }, + HeapCell, + Bag { item: TypeId }, +} + +#[derive(Clone, Debug, Default)] +pub struct TypeCache { + pub types: IdBiMap, +} diff --git a/vendor/morphic_lib/src/util/blocks.rs b/vendor/morphic_lib/src/util/blocks.rs index 79182bbd86..22c182cbfa 100644 --- a/vendor/morphic_lib/src/util/blocks.rs +++ b/vendor/morphic_lib/src/util/blocks.rs @@ -75,8 +75,8 @@ impl Blocks { tail_frag.max_val = next_val; } else { let new_tail = BlockFrag { - min_val: val_id, - max_val: next_val, + min_val: val_id.clone(), + max_val: ValId::from_index_or_panic(val_id.to_index() + 1), next: None, }; let new_tail_id = self.frags.push(new_tail); diff --git a/vendor/morphic_lib/src/util/bytes_id.rs b/vendor/morphic_lib/src/util/bytes_id.rs index 328fba47b8..b75d618271 100644 --- a/vendor/morphic_lib/src/util/bytes_id.rs +++ b/vendor/morphic_lib/src/util/bytes_id.rs @@ -4,15 +4,15 @@ macro_rules! bytes_id { $(#[$annot_borrowed:meta])* $borrowed_vis:vis $borrowed:ident; $(#[$annot_owned:meta])* $owned_vis:vis $owned:ident; ) => { - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] $(#[$annot_borrowed])* $borrowed_vis struct $borrowed<'a>($borrowed_vis &'a [u8]); - // The stack-allocated array portion of a `SmallVec` shares a union with two `usize`s, so on - // 64-bit platforms we can make the array up to 16 bytes long with no space penalty. - #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + // On 64-bit platforms we can store up to `23` bytes inline in a `SmallVec` with no space + // penalty. + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] $(#[$annot_owned])* - $owned_vis struct $owned($owned_vis ::smallvec::SmallVec<[u8; 16]>); + $owned_vis struct $owned($owned_vis ::smallvec::SmallVec<[u8; 23]>); impl $owned { fn borrowed<'a>(&'a self) -> $borrowed<'a> { @@ -25,5 +25,27 @@ macro_rules! bytes_id { $owned(::smallvec::SmallVec::from_slice(&borrowed.0)) } } + + impl<'a> ::std::fmt::Debug for $borrowed<'a> { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + // TODO: Consolidate this with render_api_ir.rs + write!(f, "{}(\"", stringify!($borrowed))?; + for &byte in self.0 { + write!(f, "{}", ::std::ascii::escape_default(byte))?; + } + write!(f, "\")")?; + Ok(()) + } + } + + impl ::std::fmt::Debug for $owned { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + // We intentionally use the name of $borrowed to render values of type $owned, + // because only the borrowed version of each bytestring-based identifier type are + // exposed/documented in the public API, and we use this debug formatting logic to + // render public-facing error messages. + write!(f, "{:?}", self.borrowed()) + } + } } } diff --git a/vendor/morphic_lib/src/util/flat_slices.rs b/vendor/morphic_lib/src/util/flat_slices.rs new file mode 100644 index 0000000000..fe72cce7e8 --- /dev/null +++ b/vendor/morphic_lib/src/util/flat_slices.rs @@ -0,0 +1,112 @@ +use std::borrow::Borrow; +use std::fmt; + +use crate::util::id_type::{self, Count, Id}; +use crate::util::id_vec::IdVec; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct SliceData { + info: SliceInfo, + + /// Determines which slice of the `flat_data` buffer contains the items of this slice. + /// + /// If the *previous* slice has `slice_end_idx == a`, and this slice has `inputs_end_idx == b`, + /// then the items of this slice are given by `flat_data[a..b]`. + slice_end_idx: usize, +} + +/// Conceptually represents a collection of the form `IdVec)>`. +/// +/// The notional `Vec` values are actually stored in a single contiguous buffer to reduce the +/// number of heap allocations. Because these values are all different slices of the same +/// underlying buffer, we refer to each tuple `(SliceInfo, Vec)` as a "slice" for the purposes of +/// this data structure's API. +/// +/// The `SliceInfo` parameter should be regarded as optional, and for some purposes it is perfectly +/// fine (stylistically speaking) to set `SliceInfo = ()`. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FlatSlices { + flat_data: Vec, + slices: IdVec>, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Slice<'a, SliceInfo, T> { + pub info: &'a SliceInfo, + pub items: &'a [T], +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SliceMut<'a, SliceInfo, T> { + pub info: &'a mut SliceInfo, + pub items: &'a mut [T], +} + +impl Default for FlatSlices { + fn default() -> Self { + Self::new() + } +} + +impl FlatSlices { + pub fn new() -> Self { + Self { + flat_data: Vec::new(), + slices: IdVec::new(), + } + } + + pub fn len(&self) -> usize { + self.slices.len() + } + + pub fn count(&self) -> Count { + self.slices.count() + } + + pub fn push_slice(&mut self, info: SliceInfo, slice: &[T]) -> SliceId + where + T: Clone, + { + self.flat_data.extend_from_slice(slice); + self.slices.push(SliceData { + info, + slice_end_idx: self.flat_data.len(), + }) + } + + fn data_range(&self, idx: SliceId) -> (usize, usize) { + let start = match id_type::decrement(idx.clone()) { + None => 0, + Some(prev_idx) => self.slices[prev_idx].slice_end_idx, + }; + let end = self.slices[idx].slice_end_idx; + (start, end) + } + + pub fn get>(&self, idx: I) -> Slice { + let (start, end) = self.data_range(idx.borrow().clone()); + Slice { + info: &self.slices[idx].info, + items: &self.flat_data[start..end], + } + } + + pub fn get_mut>(&mut self, idx: I) -> SliceMut { + let (start, end) = self.data_range(idx.borrow().clone()); + SliceMut { + info: &mut self.slices[idx].info, + items: &mut self.flat_data[start..end], + } + } +} + +impl fmt::Debug + for FlatSlices +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries(self.count().iter().map(|i| (i.clone(), self.get(i)))) + .finish() + } +} diff --git a/vendor/morphic_lib/src/util/id_bi_map.rs b/vendor/morphic_lib/src/util/id_bi_map.rs index 8b7c31621e..00b04c2142 100644 --- a/vendor/morphic_lib/src/util/id_bi_map.rs +++ b/vendor/morphic_lib/src/util/id_bi_map.rs @@ -52,6 +52,18 @@ impl IdBiMap { } } + /// Insert a value into the bi-map, or get its key if it is already present. + pub fn get_or_insert(&mut self, val: V) -> K { + match self.val_to_key.entry(val) { + Entry::Occupied(occupied) => occupied.get().clone(), + Entry::Vacant(vacant) => { + let new_index = self.key_to_val.push(vacant.key().clone()); + vacant.insert(new_index.clone()); + new_index + } + } + } + pub fn get_by_val(&self, val: &V) -> Option { self.val_to_key.get(val).cloned() } diff --git a/vendor/morphic_lib/src/util/id_type.rs b/vendor/morphic_lib/src/util/id_type.rs index 047e2886da..a0b730d746 100644 --- a/vendor/morphic_lib/src/util/id_type.rs +++ b/vendor/morphic_lib/src/util/id_type.rs @@ -98,7 +98,14 @@ impl PartialOrd for Count { } impl Count { - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl DoubleEndedIterator { (0..self.0.to_index()).map(T::from_index_unchecked) } } + +pub fn decrement(id: T) -> Option { + match id.to_index() { + 0 => None, + index => Some(T::from_index_unchecked(index - 1)), + } +} diff --git a/vendor/morphic_lib/src/util/id_vec.rs b/vendor/morphic_lib/src/util/id_vec.rs index 99a53fce74..ab222559ea 100644 --- a/vendor/morphic_lib/src/util/id_vec.rs +++ b/vendor/morphic_lib/src/util/id_vec.rs @@ -107,6 +107,13 @@ impl IdVec { } } + pub fn with_capacity(capacity: usize) -> Self { + IdVec { + key: PhantomData, + items: Vec::with_capacity(capacity), + } + } + pub fn from_items(items: Vec) -> Self { K::assert_in_range(items.len()); IdVec { diff --git a/vendor/morphic_lib/src/util/mod.rs b/vendor/morphic_lib/src/util/mod.rs index b382f997cd..d5fb572b08 100644 --- a/vendor/morphic_lib/src/util/mod.rs +++ b/vendor/morphic_lib/src/util/mod.rs @@ -8,7 +8,9 @@ pub mod bytes_id; pub mod forward_trait; pub mod blocks; +pub mod flat_slices; pub mod id_bi_map; pub mod id_vec; pub mod op_graph; pub mod replace_none; +pub mod strongly_connected; diff --git a/vendor/morphic_lib/src/util/op_graph.rs b/vendor/morphic_lib/src/util/op_graph.rs index b7f8b3ca9a..46e7b38f49 100644 --- a/vendor/morphic_lib/src/util/op_graph.rs +++ b/vendor/morphic_lib/src/util/op_graph.rs @@ -1,18 +1,7 @@ use std::borrow::Borrow; +use crate::util::flat_slices::{FlatSlices, Slice}; use crate::util::id_type::{Count, Id}; -use crate::util::id_vec::IdVec; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct NodeData { - op: Op, - - /// Determines which slice of the `flat_inputs` buffer contains the inputs to this node. - /// - /// If the *previous* op has `inputs_end_idx == a`, and this node has `inputs_end_idx == b`, - /// then the inputs to this node are given by `flat_inputs[a..b]`. - inputs_end_idx: usize, -} #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Node<'a, K, Op> { @@ -27,47 +16,45 @@ pub struct Node<'a, K, Op> { /// /// The input lists are actually stored in a single contiguous buffer to reduce the number of heap /// allocations. +/// +/// This is essentially just a newtype wrapper around `FlatSlices`. We use this wrapper to +/// represent op graphs (instead of using `FlatSlices` directly) just so that the names of types, +/// functions, and fields in the API are more meaningful for this use case. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct OpGraph { - flat_inputs: Vec, - nodes: IdVec>, + inner: FlatSlices, +} + +impl Default for OpGraph { + fn default() -> Self { + Self::new() + } } impl OpGraph { pub fn new() -> Self { Self { - flat_inputs: Vec::new(), - nodes: IdVec::new(), + inner: FlatSlices::new(), } } pub fn len(&self) -> usize { - self.nodes.len() + self.inner.len() } pub fn count(&self) -> Count { - self.nodes.count() + self.inner.count() } pub fn add_node(&mut self, op: Op, inputs: &[K]) -> K { - self.flat_inputs.extend_from_slice(inputs); - let node = NodeData { - op, - inputs_end_idx: self.flat_inputs.len(), - }; - self.nodes.push(node) + self.inner.push_slice(op, inputs) } pub fn node>(&self, idx: I) -> Node { - let node = &self.nodes[idx.borrow()]; - let inputs_start_idx = if idx.borrow().to_index() == 0 { - 0 - } else { - self.nodes[K::from_index_unchecked(idx.borrow().to_index() - 1)].inputs_end_idx - }; + let Slice { info, items } = self.inner.get(idx); Node { - op: &node.op, - inputs: &self.flat_inputs[inputs_start_idx..node.inputs_end_idx], + op: info, + inputs: items, } } } diff --git a/vendor/morphic_lib/src/util/strongly_connected.rs b/vendor/morphic_lib/src/util/strongly_connected.rs new file mode 100644 index 0000000000..39e1f590cd --- /dev/null +++ b/vendor/morphic_lib/src/util/strongly_connected.rs @@ -0,0 +1,153 @@ +use crate::util::flat_slices::FlatSlices; +use crate::util::id_type::{Count, Id}; +use crate::util::id_vec::IdVec; +use crate::util::replace_none::replace_none; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum SccKind { + Acyclic, + Cyclic, +} + +pub fn strongly_connected( + node_count: Count, + mut node_successors: impl FnMut(NodeId) -> NodeSuccessors, +) -> FlatSlices +where + SccId: Id, + NodeId: Id + Eq + Copy, + NodeSuccessors: Iterator, +{ + // We use Tarjan's algorithm, performing the depth-first search using an explicit Vec-based + // stack instead of recursion to avoid stack overflows on large graphs. + + #[derive(Clone, Copy, Debug)] + enum NodeState { + Unvisited, + OnSearchStack { index: u32, low_link: u32 }, + OnSccStack { index: u32 }, + Complete, + } + + #[derive(Clone, Copy)] + enum Action { + TryVisit { + parent: Option, + node: NodeId, + }, + FinishVisit { + parent: Option, + node: NodeId, + }, + } + + let mut sccs = FlatSlices::new(); + + let mut node_states = IdVec::filled_with(node_count, || NodeState::Unvisited); + let mut node_self_loops = IdVec::filled_with(node_count, || None); + let mut scc_stack = Vec::new(); + let mut search_stack = Vec::new(); + let mut next_index = 0; + + for search_root in node_count.iter() { + search_stack.push(Action::TryVisit { + parent: None, + node: search_root, + }); + while let Some(action) = search_stack.pop() { + match action { + Action::TryVisit { parent, node } => match node_states[node] { + NodeState::Unvisited => { + node_states[node] = NodeState::OnSearchStack { + index: next_index, + low_link: next_index, + }; + next_index += 1; + scc_stack.push(node); + + search_stack.push(Action::FinishVisit { parent, node }); + // We need to explicitly track self-loops so that when we obtain a size-1 + // SCC we can determine if it's cyclic or acyclic. + let mut has_self_loop = false; + for successor in node_successors(node) { + if successor == node { + has_self_loop = true; + } + search_stack.push(Action::TryVisit { + parent: Some(node), + node: successor, + }); + } + replace_none(&mut node_self_loops[node], has_self_loop).unwrap(); + } + + NodeState::OnSearchStack { index, low_link: _ } + | NodeState::OnSccStack { index } => { + if let Some(parent) = parent { + if let NodeState::OnSearchStack { + index: _, + low_link: parent_low_link, + } = &mut node_states[parent] + { + *parent_low_link = (*parent_low_link).min(index); + } else { + unreachable!("parent should be on search stack"); + } + } + } + + NodeState::Complete => {} + }, + + Action::FinishVisit { parent, node } => { + let (index, low_link) = + if let NodeState::OnSearchStack { index, low_link } = node_states[node] { + (index, low_link) + } else { + unreachable!("node should be on search stack"); + }; + + node_states[node] = NodeState::OnSccStack { index }; + + if let Some(parent) = parent { + if let NodeState::OnSearchStack { + index: _, + low_link: parent_low_link, + } = &mut node_states[parent] + { + *parent_low_link = (*parent_low_link).min(low_link); + } else { + unreachable!("parent should be on search stack") + } + } + + if low_link == index { + let mut scc_start = scc_stack.len(); + loop { + scc_start -= 1; + let scc_node = scc_stack[scc_start]; + debug_assert!(matches!( + node_states[scc_node], + NodeState::OnSccStack { .. } + )); + node_states[scc_node] = NodeState::Complete; + if scc_node == node { + break; + } + } + let scc_slice = &scc_stack[scc_start..]; + let scc_kind = if scc_slice.len() == 1 && !node_self_loops[node].unwrap() { + SccKind::Acyclic + } else { + SccKind::Cyclic + }; + sccs.push_slice(scc_kind, scc_slice); + scc_stack.truncate(scc_start); + } + } + } + } + } + + sccs +} diff --git a/vendor/morphic_lib/tests/basic.rs b/vendor/morphic_lib/tests/basic.rs new file mode 100644 index 0000000000..2c7531c392 --- /dev/null +++ b/vendor/morphic_lib/tests/basic.rs @@ -0,0 +1,77 @@ +use morphic_lib::{ + BlockExpr, CalleeSpecVar, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, + ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateModeVar, +}; + +#[test] +fn test_basic() { + fn run() -> Result<(), Error> { + let func1_def = { + let mut f = FuncDefBuilder::new(); + let b = f.add_block(); + f.add_update(b, UpdateModeVar(b"var1"), f.get_argument())?; + let ret = f.add_make_tuple(b, &[])?; + let arg_type = f.add_heap_cell_type(); + let ret_type = f.add_tuple_type(&[])?; + f.build(arg_type, ret_type, BlockExpr(b, ret))? + }; + + let func2_def = { + let mut f = FuncDefBuilder::new(); + let b = f.add_block(); + let cell = f.add_new_heap_cell(b)?; + let ret = f.add_call( + b, + CalleeSpecVar(b"var2"), + ModName(b"main_mod"), + FuncName(b"func1"), + cell, + )?; + let unit_type = f.add_tuple_type(&[])?; + f.build(unit_type, unit_type, BlockExpr(b, ret))? + }; + + let main_mod = { + let mut m = ModDefBuilder::new(); + m.add_func(FuncName(b"func1"), func1_def)?; + m.add_func(FuncName(b"func2"), func2_def)?; + m.build()? + }; + + let program = { + let mut p = ProgramBuilder::new(); + p.add_mod(ModName(b"main_mod"), main_mod)?; + p.add_entry_point( + EntryPointName(b"main"), + ModName(b"main_mod"), + FuncName(b"func2"), + )?; + p.build()? + }; + + let program_sol = morphic_lib::solve(program)?; + + let (_, _, func2_spec) = program_sol.entry_point_solution(EntryPointName(b"main"))?; + + let main_mod_sol = program_sol.mod_solutions(ModName(b"main_mod"))?; + + let func2_sol = main_mod_sol + .func_solutions(FuncName(b"func2"))? + .spec(&func2_spec)?; + + let func1_spec = func2_sol.callee_spec(CalleeSpecVar(b"var2"))?; + + let func1_sol = main_mod_sol + .func_solutions(FuncName(b"func1"))? + .spec(&func1_spec)?; + + let _update_mode = func1_sol.update_mode(UpdateModeVar(b"var1"))?; + + Ok(()) + } + + let result = run(); + if let Err(err) = result { + panic!("error: {}", err); + } +} diff --git a/vendor/morphic_lib/tests/recursive.rs b/vendor/morphic_lib/tests/recursive.rs new file mode 100644 index 0000000000..9e50172a50 --- /dev/null +++ b/vendor/morphic_lib/tests/recursive.rs @@ -0,0 +1,95 @@ +use morphic_lib::{ + BlockExpr, CalleeSpecVar, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, + ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateModeVar, +}; + +#[test] +fn test_recursive() { + fn run() -> Result<(), Error> { + let rec_def = { + let mut f = FuncDefBuilder::new(); + let b = f.add_block(); + let arg = f.get_argument(); + let case1 = { + let b = f.add_block(); + BlockExpr(b, arg) + }; + let case2 = { + let b = f.add_block(); + f.add_update(b, UpdateModeVar(b"mode"), arg)?; + let new = f.add_new_heap_cell(b)?; + let result = f.add_call( + b, + CalleeSpecVar(b"call"), + ModName(b"main"), + FuncName(b"rec"), + new, + )?; + BlockExpr(b, result) + }; + let result = f.add_choice(b, &[case1, case2])?; + let heap_cell_type = f.add_heap_cell_type(); + f.build(heap_cell_type, heap_cell_type, BlockExpr(b, result))? + }; + + let main_def = { + let mut f = FuncDefBuilder::new(); + let b = f.add_block(); + let init = f.add_new_heap_cell(b)?; + let final_ = f.add_call( + b, + CalleeSpecVar(b"call"), + ModName(b"main"), + FuncName(b"rec"), + init, + )?; + f.add_touch(b, final_)?; + let result = f.add_make_tuple(b, &[])?; + let unit_type = f.add_tuple_type(&[])?; + f.build(unit_type, unit_type, BlockExpr(b, result))? + }; + + let mod_ = { + let mut m = ModDefBuilder::new(); + m.add_func(FuncName(b"rec"), rec_def)?; + m.add_func(FuncName(b"main"), main_def)?; + m.build()? + }; + + let program = { + let mut p = ProgramBuilder::new(); + p.add_mod(ModName(b"main"), mod_)?; + p.add_entry_point( + EntryPointName(b"entry"), + ModName(b"main"), + FuncName(b"main"), + )?; + p.build()? + }; + + let program_sol = morphic_lib::solve(program)?; + + let (_, _, main_spec) = program_sol.entry_point_solution(EntryPointName(b"entry"))?; + + let main_mod_sol = program_sol.mod_solutions(ModName(b"main"))?; + + let main_sol = main_mod_sol + .func_solutions(FuncName(b"main"))? + .spec(&main_spec)?; + + let rec_spec = main_sol.callee_spec(CalleeSpecVar(b"call"))?; + + let rec_sol = main_mod_sol + .func_solutions(FuncName(b"rec"))? + .spec(&rec_spec)?; + + let _update_mode = rec_sol.update_mode(UpdateModeVar(b"mode"))?; + + Ok(()) + } + + let result = run(); + if let Err(err) = result { + panic!("error: {}", err); + } +}