Merge branch 'trunk' into dec-div

This commit is contained in:
Richard Feldman 2021-06-16 23:54:00 -04:00 committed by GitHub
commit 94dd8a7253
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 2448 additions and 168 deletions

View file

@ -519,7 +519,7 @@ where
} }
Stmt::Join { Stmt::Join {
parameters, parameters,
continuation, body: continuation,
remainder, remainder,
.. ..
} => { } => {

View file

@ -830,9 +830,10 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
CallType::HigherOrderLowLevel { CallType::HigherOrderLowLevel {
op, op,
closure_layout,
function_owns_closure_data, function_owns_closure_data,
specialization_id, specialization_id,
arg_layouts,
ret_layout,
.. ..
} => { } => {
let bytes = specialization_id.to_bytes(); let bytes = specialization_id.to_bytes();
@ -846,8 +847,9 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
scope, scope,
layout, layout,
*op, *op,
*closure_layout,
func_spec, func_spec,
arg_layouts,
ret_layout,
*function_owns_closure_data, *function_owns_closure_data,
arguments, arguments,
) )
@ -1436,6 +1438,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
structure, structure,
wrapped: Wrapped::RecordOrSingleTagUnion, wrapped: Wrapped::RecordOrSingleTagUnion,
.. ..
}
| AccessAtIndex {
index,
structure,
wrapped: Wrapped::LikeARoseTree,
..
} => { } => {
// extract field from a record // extract field from a record
match load_symbol_and_layout(scope, structure) { match load_symbol_and_layout(scope, structure) {
@ -2137,7 +2145,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
id, id,
parameters, parameters,
remainder, remainder,
continuation, body: continuation,
} => { } => {
let builder = env.builder; let builder = env.builder;
let context = env.context; let context = env.context;
@ -3161,7 +3169,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
let it = procedures.iter().map(|x| x.1); let it = procedures.iter().map(|x| x.1);
let solutions = match roc_mono::alias_analysis::spec_program(entry_point, it) { 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, Ok(solutions) => solutions,
}; };
@ -3815,8 +3823,9 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
return_layout: &Layout<'a>, return_layout: &Layout<'a>,
op: LowLevel, op: LowLevel,
function_layout: Layout<'a>,
func_spec: FuncSpec, func_spec: FuncSpec,
argument_layouts: &[Layout<'a>],
result_layout: &Layout<'a>,
function_owns_closure_data: bool, function_owns_closure_data: bool,
args: &[Symbol], args: &[Symbol],
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
@ -3828,6 +3837,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
macro_rules! passed_function_at_index { macro_rules! passed_function_at_index {
($index:expr) => {{ ($index:expr) => {{
let function_symbol = args[$index]; 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) 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, env,
layout_ids, layout_ids,
roc_function_call, roc_function_call,
&function_layout, result_layout,
list, list,
before_layout, before_layout,
after_layout, after_layout,
@ -4139,7 +4149,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
roc_function_call, roc_function_call,
&function_layout, result_layout,
list, list,
before_layout, before_layout,
after_layout, after_layout,

View file

@ -601,18 +601,12 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>, roc_function_call: RocFunctionCall<'ctx>,
function_layout: &Layout<'a>, // Layout of the `Result after *`
result_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>, list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>, before_layout: &Layout<'a>,
after_layout: &Layout<'a>, after_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> 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); let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
call_bitcode_fn( call_bitcode_fn(
@ -638,18 +632,12 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>, roc_function_call: RocFunctionCall<'ctx>,
function_layout: &Layout<'a>, // Layout of the `Result * err`
result_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>, list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>, before_layout: &Layout<'a>,
after_layout: &Layout<'a>, after_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> 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); let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
call_bitcode_fn( call_bitcode_fn(

View file

@ -19,19 +19,22 @@ pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STA
const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; 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) 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>( pub fn func_name_bytes_help<'a, I>(
symbol: Symbol, symbol: Symbol,
argument_layouts: I, argument_layouts: I,
return_layout: Layout<'a>, return_layout: Layout<'a>,
) -> [u8; 16] ) -> [u8; SIZE]
where where
I: Iterator<Item = Layout<'a>>, I: Iterator<Item = Layout<'a>>,
{ {
let mut name_bytes = [0u8; 16]; let mut name_bytes = [0u8; SIZE];
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::hash::Hash; use std::hash::Hash;
@ -75,9 +78,27 @@ where
*target = *source; *target = *source;
} }
if DEBUG {
for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() {
name_bytes[25 + i] = c as u8;
}
}
name_bytes 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>( pub fn spec_program<'a, I>(
entry_point: crate::ir::EntryPoint<'a>, entry_point: crate::ir::EntryPoint<'a>,
procs: I, procs: I,
@ -102,15 +123,34 @@ where
m.add_const(STATIC_STR_NAME, static_str_def)?; m.add_const(STATIC_STR_NAME, static_str_def)?;
// the entry point wrapper // 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_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)?; m.add_func(entry_point_name, entry_point_function)?;
// all other functions // all other functions
for proc in procs { 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)?; let spec = proc_spec(proc)?;
m.add_func(FuncName(&func_name_bytes(proc)), spec)?; m.add_func(func_name, spec)?;
} }
m.build()? m.build()?
@ -126,7 +166,9 @@ where
p.build()? p.build()?
}; };
// eprintln!("{}", program.to_source_string()); if DEBUG {
eprintln!("{}", program.to_source_string());
}
morphic_lib::solve(program) morphic_lib::solve(program)
} }
@ -198,11 +240,25 @@ fn stmt_spec(
use Stmt::*; use Stmt::*;
match stmt { match stmt {
Let(symbol, expr, layout, continuation) => { Let(symbol, expr, expr_layout, mut continuation) => {
let value_id = expr_spec(builder, env, block, layout, expr)?; let value_id = expr_spec(builder, env, block, expr_layout, expr)?;
env.symbols.insert(*symbol, value_id); 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)?; let result = stmt_spec(builder, env, block, layout, continuation)?;
for symbol in queue {
env.symbols.remove(symbol); env.symbols.remove(symbol);
}
Ok(result) Ok(result)
} }
@ -235,7 +291,7 @@ fn stmt_spec(
cond_layout: _, cond_layout: _,
branches, branches,
default_branch, default_branch,
ret_layout, ret_layout: _lies,
} => { } => {
let mut cases = Vec::with_capacity(branches.len() + 1); let mut cases = Vec::with_capacity(branches.len() + 1);
@ -246,7 +302,7 @@ fn stmt_spec(
for branch in it { for branch in it {
let block = builder.add_block(); 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)); cases.push(BlockExpr(block, value_id));
} }
@ -282,7 +338,7 @@ fn stmt_spec(
Join { Join {
id, id,
parameters, parameters,
continuation, body,
remainder, remainder,
} => { } => {
let mut type_ids = Vec::new(); let mut type_ids = Vec::new();
@ -298,27 +354,33 @@ fn stmt_spec(
let (jpid, jp_argument) = let (jpid, jp_argument) =
builder.declare_continuation(block, jp_arg_type_id, ret_type_id)?; builder.declare_continuation(block, jp_arg_type_id, ret_type_id)?;
let join_body_sub_block = { // 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); 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 = {
let jp_body_block = builder.add_block(); let jp_body_block = builder.add_block();
// unpack the argument // unpack the argument
for (i, p) in parameters.iter().enumerate() { for (i, p) in parameters.iter().enumerate() {
let value_id = let value_id =
builder.add_get_tuple_field(jp_body_block, jp_argument, i as u32)?; builder.add_get_tuple_field(jp_body_block, jp_argument, i as u32)?;
env.symbols.insert(p.symbol, value_id); 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) 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); env.join_points.remove(id);
builder.define_continuation(jpid, join_body_sub_block)?; builder.define_continuation(jpid, join_body_sub_block)?;
@ -423,7 +485,7 @@ fn call_spec(
), ),
HigherOrderLowLevel { HigherOrderLowLevel {
specialization_id, specialization_id,
closure_layout: _, closure_env_layout,
op, op,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -458,15 +520,160 @@ fn call_spec(
DictWalk => { DictWalk => {
let dict = env.symbols[&call.arguments[0]]; let dict = env.symbols[&call.arguments[0]];
let default = env.symbols[&call.arguments[1]]; 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 bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?; 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)?; 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 // fake a call to the function argument
// to make sure the function is specialized // to make sure the function is specialized
@ -653,7 +860,7 @@ fn expr_spec(
Struct(fields) => build_tuple_value(builder, env, block, fields), Struct(fields) => build_tuple_value(builder, env, block, fields),
AccessAtIndex { AccessAtIndex {
index, index,
field_layouts, field_layouts: _,
structure, structure,
wrapped, wrapped,
} => { } => {
@ -673,17 +880,20 @@ fn expr_spec(
Wrapped::RecordOrSingleTagUnion => { Wrapped::RecordOrSingleTagUnion => {
builder.add_get_tuple_field(block, value_id, *index as u32) builder.add_get_tuple_field(block, value_id, *index as u32)
} }
Wrapped::MultiTagUnion => { Wrapped::LikeARoseTree => {
// 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);
if hacky_is_recursive {
let result_type = layout_spec(builder, layout)?; let result_type = layout_spec(builder, layout)?;
builder.add_unknown_with(block, &[value_id], result_type) builder.add_unknown_with(block, &[value_id], result_type)
} else {
builder.add_get_tuple_field(block, value_id, *index as u32)
} }
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);
// if hacky_is_recursive {
// 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)?; 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 => { EmptyArray => {
@ -827,8 +1039,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeI
fn str_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> { fn str_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
let cell_id = builder.add_heap_cell_type(); let cell_id = builder.add_heap_cell_type();
let len_id = builder.add_tuple_type(&[])?; builder.add_tuple_type(&[cell_id])
builder.add_tuple_type(&[cell_id, len_id])
} }
// const OK_TAG_ID: u8 = 1u8; // const OK_TAG_ID: u8 = 1u8;

View file

@ -193,7 +193,7 @@ impl<'a> ParamMap<'a> {
id: j, id: j,
parameters: xs, parameters: xs,
remainder: v, remainder: v,
continuation: b, body: b,
} => { } => {
let already_in_there = self let already_in_there = self
.items .items
@ -393,15 +393,20 @@ impl<'a> BorrowInfState<'a> {
} }
HigherOrderLowLevel { HigherOrderLowLevel {
op, closure_layout, .. op,
arg_layouts,
ret_layout,
..
} => { } => {
use roc_module::low_level::LowLevel::*; use roc_module::low_level::LowLevel::*;
debug_assert!(op.is_higher_order()); debug_assert!(op.is_higher_order());
let closure_layout = Layout::FunctionPointer(arg_layouts, ret_layout);
match op { match op {
ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => { 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) => { Some(function_ps) => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -417,7 +422,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
ListMapWithIndex => { ListMapWithIndex => {
match self.param_map.get_symbol(arguments[1], *closure_layout) { match self.param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[1].borrow { if !function_ps[1].borrow {
@ -432,7 +437,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(), 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) => { Some(function_ps) => {
// own the lists if the function wants to own the element // own the lists if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -450,7 +455,7 @@ impl<'a> BorrowInfState<'a> {
} }
None => unreachable!(), 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) => { Some(function_ps) => {
// own the lists if the function wants to own the element // own the lists if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -471,7 +476,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(), None => unreachable!(),
}, },
ListSortWith => { ListSortWith => {
match self.param_map.get_symbol(arguments[1], *closure_layout) { match self.param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// always own the input list // always own the input list
self.own_var(arguments[0]); self.own_var(arguments[0]);
@ -485,7 +490,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { 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) => { Some(function_ps) => {
// own the data structure if the function wants to own the element // own the data structure if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -618,7 +623,7 @@ impl<'a> BorrowInfState<'a> {
id: j, id: j,
parameters: ys, parameters: ys,
remainder: v, remainder: v,
continuation: b, body: b,
} => { } => {
let old = self.param_set.clone(); let old = self.param_set.clone();
self.update_param_set(ys); self.update_param_set(ys);

View file

@ -597,7 +597,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end); start.extend(end);
} }
} }
Wrapped::RecordOrSingleTagUnion => { Wrapped::RecordOrSingleTagUnion | Wrapped::LikeARoseTree => {
let sub_positions = arguments.into_iter().enumerate().map( let sub_positions = arguments.into_iter().enumerate().map(
|(index, (pattern, _))| { |(index, (pattern, _))| {
let mut new_path = path.to_vec(); let mut new_path = path.to_vec();
@ -956,7 +956,7 @@ pub fn optimize_when<'a>(
stmt = Stmt::Join { stmt = Stmt::Join {
id, id,
parameters: &[], parameters: &[],
continuation: env.arena.alloc(body), body: env.arena.alloc(body),
remainder: env.arena.alloc(stmt), remainder: env.arena.alloc(stmt),
}; };
} }
@ -1329,7 +1329,7 @@ fn compile_guard<'a>(
id, id,
parameters: arena.alloc([param]), parameters: arena.alloc([param]),
remainder: stmt, remainder: stmt,
continuation: arena.alloc(cond), body: arena.alloc(cond),
} }
} }
@ -1622,7 +1622,7 @@ fn decide_to_branching<'a>(
Stmt::Join { Stmt::Join {
id: fail_jp_id, id: fail_jp_id,
parameters: &[], parameters: &[],
continuation: fail, body: fail,
remainder: arena.alloc(test_stmt), remainder: arena.alloc(test_stmt),
} }
} }

View file

@ -622,7 +622,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
Join { Join {
id, id,
parameters, parameters,
continuation, body: continuation,
remainder, remainder,
} => { } => {
let continuation = expand_and_cancel(env, continuation); 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 { let stmt = Join {
id: *id, id: *id,
parameters, parameters,
continuation, body: continuation,
remainder, remainder,
}; };

View file

@ -64,7 +64,7 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>)
Join { Join {
parameters, parameters,
continuation, body: continuation,
remainder, remainder,
.. ..
} => { } => {
@ -455,7 +455,7 @@ impl<'a> Context<'a> {
HigherOrderLowLevel { HigherOrderLowLevel {
op, op,
closure_layout, closure_env_layout,
specialization_id, specialization_id,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -467,7 +467,7 @@ impl<'a> Context<'a> {
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
HigherOrderLowLevel { HigherOrderLowLevel {
op: *op, op: *op,
closure_layout: *closure_layout, closure_env_layout: *closure_env_layout,
function_owns_closure_data: true, function_owns_closure_data: true,
specialization_id: *specialization_id, specialization_id: *specialization_id,
arg_layouts, arg_layouts,
@ -497,12 +497,14 @@ impl<'a> Context<'a> {
const FUNCTION: bool = BORROWED; const FUNCTION: bool = BORROWED;
const CLOSURE_DATA: bool = BORROWED; const CLOSURE_DATA: bool = BORROWED;
let function_layout = Layout::FunctionPointer(arg_layouts, ret_layout);
match op { match op {
roc_module::low_level::LowLevel::ListMap roc_module::low_level::LowLevel::ListMap
| roc_module::low_level::LowLevel::ListKeepIf | roc_module::low_level::LowLevel::ListKeepIf
| roc_module::low_level::LowLevel::ListKeepOks | roc_module::low_level::LowLevel::ListKeepOks
| roc_module::low_level::LowLevel::ListKeepErrs => { | 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) => { Some(function_ps) => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
@ -524,7 +526,7 @@ impl<'a> Context<'a> {
} }
} }
roc_module::low_level::LowLevel::ListMapWithIndex => { 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) => { Some(function_ps) => {
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];
@ -545,7 +547,7 @@ impl<'a> Context<'a> {
} }
} }
roc_module::low_level::LowLevel::ListMap2 => { 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) => { Some(function_ps) => {
let borrows = [ let borrows = [
function_ps[0].borrow, function_ps[0].borrow,
@ -572,7 +574,7 @@ impl<'a> Context<'a> {
} }
} }
roc_module::low_level::LowLevel::ListMap3 => { 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) => { Some(function_ps) => {
let borrows = [ let borrows = [
function_ps[0].borrow, function_ps[0].borrow,
@ -601,7 +603,7 @@ impl<'a> Context<'a> {
} }
} }
roc_module::low_level::LowLevel::ListSortWith => { 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) => { Some(function_ps) => {
let borrows = [OWNED, FUNCTION, CLOSURE_DATA]; 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::ListWalkUntil
| roc_module::low_level::LowLevel::ListWalkBackwards | roc_module::low_level::LowLevel::ListWalkBackwards
| roc_module::low_level::LowLevel::DictWalk => { | 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) => { Some(function_ps) => {
// borrow data structure based on first argument of the folded function // borrow data structure based on first argument of the folded function
// borrow the default based on second 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, id: j,
parameters: _, parameters: _,
remainder: b, remainder: b,
continuation: v, body: v,
} => { } => {
// get the parameters with borrow signature // get the parameters with borrow signature
let xs = self.param_map.get_join_point(*j); let xs = self.param_map.get_join_point(*j);
@ -1000,7 +1002,7 @@ impl<'a> Context<'a> {
id: *j, id: *j,
parameters: xs, parameters: xs,
remainder: b, remainder: b,
continuation: v, body: v,
}), }),
b_live_vars, b_live_vars,
) )
@ -1143,7 +1145,7 @@ pub fn collect_stmt(
id: j, id: j,
parameters, parameters,
remainder: b, remainder: b,
continuation: v, body: v,
} => { } => {
let mut j_live_vars = collect_stmt(v, jp_live_vars, MutSet::default()); let mut j_live_vars = collect_stmt(v, jp_live_vars, MutSet::default());
for param in parameters.iter() { for param in parameters.iter() {

View file

@ -890,9 +890,10 @@ pub enum Stmt<'a> {
Join { Join {
id: JoinPointId, id: JoinPointId,
parameters: &'a [Param<'a>], parameters: &'a [Param<'a>],
/// does not contain jumps to this id /// body of the join point
continuation: &'a Stmt<'a>, /// what happens after _jumping to_ the join point
/// the "body" of the join point, contains the jumps to this id body: &'a Stmt<'a>,
/// what happens after _defining_ the join point
remainder: &'a Stmt<'a>, remainder: &'a Stmt<'a>,
}, },
Jump(JoinPointId, &'a [Symbol]), Jump(JoinPointId, &'a [Symbol]),
@ -1013,6 +1014,8 @@ pub enum Wrapped {
EmptyRecord, EmptyRecord,
SingleElementRecord, SingleElementRecord,
RecordOrSingleTagUnion, RecordOrSingleTagUnion,
/// Like a rose tree; recursive, but only one tag
LikeARoseTree,
MultiTagUnion, MultiTagUnion,
} }
@ -1045,7 +1048,7 @@ impl Wrapped {
}, },
_ => Some(Wrapped::MultiTagUnion), _ => Some(Wrapped::MultiTagUnion),
}, },
NonNullableUnwrapped(_) => Some(Wrapped::RecordOrSingleTagUnion), NonNullableUnwrapped(_) => Some(Wrapped::LikeARoseTree),
NullableWrapped { .. } | NullableUnwrapped { .. } => { NullableWrapped { .. } | NullableUnwrapped { .. } => {
Some(Wrapped::MultiTagUnion) Some(Wrapped::MultiTagUnion)
@ -1151,7 +1154,7 @@ pub enum CallType<'a> {
HigherOrderLowLevel { HigherOrderLowLevel {
op: LowLevel, op: LowLevel,
/// the layout of the closure argument, if any /// the layout of the closure argument, if any
closure_layout: Layout<'a>, closure_env_layout: Option<Layout<'a>>,
/// specialization id of the function argument /// specialization id of the function argument
specialization_id: CallSpecId, specialization_id: CallSpecId,
/// does the function need to own the closure data /// does the function need to own the closure data
@ -1476,7 +1479,7 @@ impl<'a> Stmt<'a> {
Join { Join {
id, id,
parameters, parameters,
continuation, body: continuation,
remainder, remainder,
} => { } => {
let it = parameters.iter().map(|p| symbol_to_doc(alloc, p.symbol)); 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 arena = $env.arena;
let function_layout = arena.alloc(top_level).full();
let arg_layouts = top_level.arguments; let arg_layouts = top_level.arguments;
let ret_layout = top_level.result; let ret_layout = top_level.result;
@ -2723,10 +2724,10 @@ macro_rules! match_on_closure_argument {
$env, $env,
lambda_set, lambda_set,
$closure_data_symbol, $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 { call_type: CallType::HigherOrderLowLevel {
op: $op, op: $op,
closure_layout: function_layout, closure_env_layout,
specialization_id, specialization_id,
function_owns_closure_data: false, function_owns_closure_data: false,
arg_layouts, arg_layouts,
@ -2734,7 +2735,6 @@ macro_rules! match_on_closure_argument {
}, },
arguments: arena.alloc([$($x,)* top_level_function, closure_data]), arguments: arena.alloc([$($x,)* top_level_function, closure_data]),
}, },
function_layout,
$layout, $layout,
$assigned, $assigned,
$hole, $hole,
@ -3328,7 +3328,7 @@ pub fn with_hole<'a>(
id, id,
parameters: env.arena.alloc([param]), parameters: env.arena.alloc([param]),
remainder: env.arena.alloc(stmt), remainder: env.arena.alloc(stmt),
continuation: hole, body: hole,
} }
} }
} }
@ -3381,7 +3381,7 @@ pub fn with_hole<'a>(
id, id,
parameters: env.arena.alloc([param]), parameters: env.arena.alloc([param]),
remainder: env.arena.alloc(stmt), 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 field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena); let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
@ -5327,7 +5330,7 @@ fn substitute_in_stmt_help<'a>(
id, id,
parameters, parameters,
remainder, remainder,
continuation, body: continuation,
} => { } => {
let opt_remainder = substitute_in_stmt_help(arena, remainder, subs); let opt_remainder = substitute_in_stmt_help(arena, remainder, subs);
let opt_continuation = substitute_in_stmt_help(arena, continuation, subs); let opt_continuation = substitute_in_stmt_help(arena, continuation, subs);
@ -5340,7 +5343,7 @@ fn substitute_in_stmt_help<'a>(
id: *id, id: *id,
parameters, parameters,
remainder, remainder,
continuation, body: continuation,
})) }))
} else { } else {
None None
@ -7013,7 +7016,10 @@ fn from_can_pattern_help<'a>(
union, union,
} }
} }
Unwrapped(_, field_layouts) => { Unwrapped {
arguments: field_layouts,
..
} => {
let union = crate::exhaustive::Union { let union = crate::exhaustive::Union {
render_as: RenderAs::Tag, render_as: RenderAs::Tag,
alternatives: vec![Ctor { alternatives: vec![Ctor {
@ -7708,13 +7714,12 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
lambda_set: LambdaSet<'a>, lambda_set: LambdaSet<'a>,
closure_data_symbol: Symbol, closure_data_symbol: Symbol,
to_lowlevel_call: ToLowLevelCall, to_lowlevel_call: ToLowLevelCall,
function_layout: Layout<'a>,
return_layout: Layout<'a>, return_layout: Layout<'a>,
assigned: Symbol, assigned: Symbol,
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> ) -> Stmt<'a>
where where
ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy, ToLowLevelCall: Fn(Symbol, Symbol, Option<Layout<'a>>, CallSpecId) -> Call<'a> + Copy,
{ {
match lambda_set.runtime_representation() { match lambda_set.runtime_representation() {
Layout::Union(_) => { Layout::Union(_) => {
@ -7726,8 +7731,8 @@ where
closure_tag_id_symbol, closure_tag_id_symbol,
Layout::Builtin(crate::layout::TAG_SIZE), Layout::Builtin(crate::layout::TAG_SIZE),
closure_data_symbol, closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call, to_lowlevel_call,
function_layout,
return_layout, return_layout,
assigned, assigned,
hole, hole,
@ -7755,7 +7760,7 @@ where
let call = to_lowlevel_call( let call = to_lowlevel_call(
function_symbol, function_symbol,
closure_data_symbol, closure_data_symbol,
function_layout, lambda_set.is_represented(),
call_spec_id, call_spec_id,
); );
@ -7770,8 +7775,8 @@ where
closure_tag_id_symbol, closure_tag_id_symbol,
Layout::Builtin(Builtin::Int1), Layout::Builtin(Builtin::Int1),
closure_data_symbol, closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call, to_lowlevel_call,
function_layout,
return_layout, return_layout,
assigned, assigned,
hole, hole,
@ -7786,8 +7791,8 @@ where
closure_tag_id_symbol, closure_tag_id_symbol,
Layout::Builtin(Builtin::Int8), Layout::Builtin(Builtin::Int8),
closure_data_symbol, closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call, to_lowlevel_call,
function_layout,
return_layout, return_layout,
assigned, assigned,
hole, hole,
@ -7804,14 +7809,14 @@ fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>(
closure_tag_id_symbol: Symbol, closure_tag_id_symbol: Symbol,
closure_tag_id_layout: Layout<'a>, closure_tag_id_layout: Layout<'a>,
closure_data_symbol: Symbol, closure_data_symbol: Symbol,
closure_env_layout: Option<Layout<'a>>,
to_lowlevel_call: ToLowLevelCall, to_lowlevel_call: ToLowLevelCall,
function_layout: Layout<'a>,
return_layout: Layout<'a>, return_layout: Layout<'a>,
assigned: Symbol, assigned: Symbol,
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> ) -> Stmt<'a>
where where
ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy, ToLowLevelCall: Fn(Symbol, Symbol, Option<Layout<'a>>, CallSpecId) -> Call<'a> + Copy,
{ {
debug_assert!(!lambda_set.is_empty()); debug_assert!(!lambda_set.is_empty());
@ -7828,7 +7833,7 @@ where
let call = to_lowlevel_call( let call = to_lowlevel_call(
*function_symbol, *function_symbol,
closure_data_symbol, closure_data_symbol,
function_layout, closure_env_layout,
call_spec_id, call_spec_id,
); );
let stmt = build_call(env, call, assigned, return_layout, env.arena.alloc(hole)); let stmt = build_call(env, call, assigned, return_layout, env.arena.alloc(hole));
@ -7859,7 +7864,7 @@ where
Stmt::Join { Stmt::Join {
id: join_point_id, id: join_point_id,
parameters: &*env.arena.alloc([param]), parameters: &*env.arena.alloc([param]),
continuation: hole, body: hole,
remainder: env.arena.alloc(switch), remainder: env.arena.alloc(switch),
} }
} }
@ -8017,7 +8022,7 @@ fn union_lambda_set_to_switch<'a>(
Stmt::Join { Stmt::Join {
id: join_point_id, id: join_point_id,
parameters: &*env.arena.alloc([param]), parameters: &*env.arena.alloc([param]),
continuation: hole, body: hole,
remainder: env.arena.alloc(switch), remainder: env.arena.alloc(switch),
} }
} }
@ -8161,7 +8166,7 @@ fn enum_lambda_set_to_switch<'a>(
Stmt::Join { Stmt::Join {
id: join_point_id, id: join_point_id,
parameters: &*env.arena.alloc([param]), parameters: &*env.arena.alloc([param]),
continuation: hole, body: hole,
remainder: env.arena.alloc(switch), 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_symbol: Symbol,
closure_tag_id_layout: Layout<'a>, closure_tag_id_layout: Layout<'a>,
closure_data_symbol: Symbol, closure_data_symbol: Symbol,
closure_env_layout: Option<Layout<'a>>,
to_lowlevel_call: ToLowLevelCall, to_lowlevel_call: ToLowLevelCall,
function_layout: Layout<'a>,
return_layout: Layout<'a>, return_layout: Layout<'a>,
assigned: Symbol, assigned: Symbol,
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> ) -> Stmt<'a>
where where
ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy, ToLowLevelCall: Fn(Symbol, Symbol, Option<Layout<'a>>, CallSpecId) -> Call<'a> + Copy,
{ {
debug_assert!(!lambda_set.is_empty()); debug_assert!(!lambda_set.is_empty());
@ -8253,7 +8258,7 @@ where
let call = to_lowlevel_call( let call = to_lowlevel_call(
*function_symbol, *function_symbol,
closure_data_symbol, closure_data_symbol,
function_layout, closure_env_layout,
call_spec_id, call_spec_id,
); );
let stmt = build_call( let stmt = build_call(
@ -8290,7 +8295,7 @@ where
Stmt::Join { Stmt::Join {
id: join_point_id, id: join_point_id,
parameters: &*env.arena.alloc([param]), parameters: &*env.arena.alloc([param]),
continuation: hole, body: hole,
remainder: env.arena.alloc(switch), remainder: env.arena.alloc(switch),
} }
} }

View file

@ -143,6 +143,14 @@ impl<'a> LambdaSet<'a> {
*self.representation *self.representation
} }
pub fn is_represented(&self) -> Option<Layout<'a>> {
if let Layout::Struct(&[]) = self.representation {
None
} else {
Some(*self.representation)
}
}
pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> { pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> {
debug_assert!( debug_assert!(
self.set.iter().any(|(s, _)| *s == function_symbol), self.set.iter().any(|(s, _)| *s == function_symbol),
@ -281,7 +289,9 @@ impl<'a> LambdaSet<'a> {
Unit | UnitWithArguments => Layout::Struct(&[]), Unit | UnitWithArguments => Layout::Struct(&[]),
BoolUnion { .. } => Layout::Builtin(Builtin::Int1), BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
ByteUnion(_) => Layout::Builtin(Builtin::Int8), 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) => { Wrapped(variant) => {
use WrappedVariant::*; use WrappedVariant::*;
@ -1266,9 +1276,15 @@ pub enum UnionVariant<'a> {
Never, Never,
Unit, Unit,
UnitWithArguments, UnitWithArguments,
BoolUnion { ttrue: TagName, ffalse: TagName }, BoolUnion {
ttrue: TagName,
ffalse: TagName,
},
ByteUnion(Vec<'a, TagName>), ByteUnion(Vec<'a, TagName>),
Unwrapped(TagName, Vec<'a, Layout<'a>>), Unwrapped {
tag_name: TagName,
arguments: Vec<'a, Layout<'a>>,
},
Wrapped(WrappedVariant<'a>), Wrapped(WrappedVariant<'a>),
} }
@ -1486,7 +1502,10 @@ pub fn union_sorted_tags_help<'a>(
fields: layouts.into_bump_slice(), fields: layouts.into_bump_slice(),
}) })
} else { } else {
UnionVariant::Unwrapped(tag_name, layouts) UnionVariant::Unwrapped {
tag_name,
arguments: layouts,
}
} }
} }
num_tags => { num_tags => {
@ -1638,7 +1657,10 @@ pub fn layout_from_tag_union<'a>(
Unit | UnitWithArguments => Layout::Struct(&[]), Unit | UnitWithArguments => Layout::Struct(&[]),
BoolUnion { .. } => Layout::Builtin(Builtin::Int1), BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
ByteUnion(_) => Layout::Builtin(Builtin::Int8), ByteUnion(_) => Layout::Builtin(Builtin::Int8),
Unwrapped(_, mut field_layouts) => { Unwrapped {
arguments: mut field_layouts,
..
} => {
if field_layouts.len() == 1 { if field_layouts.len() == 1 {
field_layouts.pop().unwrap() field_layouts.pop().unwrap()
} else { } else {

View file

@ -60,7 +60,7 @@ pub fn make_tail_recursive<'a>(
id, id,
remainder: jump, remainder: jump,
parameters: params, parameters: params,
continuation: new, body: new,
} }
} }
} }
@ -160,7 +160,7 @@ fn insert_jumps<'a>(
id, id,
parameters, parameters,
remainder, remainder,
continuation, body: continuation,
} => { } => {
let opt_remainder = insert_jumps(arena, remainder, goal_id, needle); let opt_remainder = insert_jumps(arena, remainder, goal_id, needle);
let opt_continuation = insert_jumps(arena, continuation, goal_id, needle); let opt_continuation = insert_jumps(arena, continuation, goal_id, needle);
@ -173,7 +173,7 @@ fn insert_jumps<'a>(
id: *id, id: *id,
parameters, parameters,
remainder, remainder,
continuation, body: continuation,
})) }))
} else { } else {
None None

View file

@ -6,7 +6,5 @@ platform examples/quicksort
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {} effects fx.Effect {}
update : Model -> Model
mainForHost : List I64 -> List I64 mainForHost : List I64 -> List I64
mainForHost = \list -> quicksort list mainForHost = \list -> quicksort list

View file

@ -2,6 +2,7 @@ use sha2::{digest::Digest, Sha256};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::collections::{btree_map::Entry, BTreeMap}; use std::collections::{btree_map::Entry, BTreeMap};
use crate::preprocess;
use crate::render_api_ir; use crate::render_api_ir;
use crate::util::blocks::Blocks; use crate::util::blocks::Blocks;
use crate::util::id_bi_map::IdBiMap; use crate::util::id_bi_map::IdBiMap;
@ -57,6 +58,8 @@ enum ErrorKind {
ModNotFound(ModNameBuf), ModNotFound(ModNameBuf),
#[error("entry point {0:?} not found in program")] #[error("entry point {0:?} not found in program")]
EntryPointNotFound(EntryPointNameBuf), EntryPointNotFound(EntryPointNameBuf),
#[error("{0}")]
PreprocessError(preprocess::Error),
} }
#[derive(Clone, thiserror::Error, Debug)] #[derive(Clone, thiserror::Error, Debug)]
@ -1550,6 +1553,8 @@ fn populate_specs(
} }
pub fn solve(program: Program) -> Result<Solutions> { pub fn solve(program: Program) -> Result<Solutions> {
preprocess::preprocess(&program).map_err(ErrorKind::PreprocessError)?;
Ok(Solutions { Ok(Solutions {
mods: program mods: program
.mods .mods

303
vendor/morphic_lib/src/ir.rs vendored Normal file
View file

@ -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<T>` for some `T`
EmptyBag,
// 2 inputs: `(bag, to_insert)`
// `bag` has type `Bag<T>`
// `to_insert` has type `T`
// Output type is `Bag<T>`
BagInsert,
// 1 input
// Input type is `Bag<T>`
// Output type is `T`
BagGet,
// 1 input
// Input type is `Bag<T>`
// Output type is `(Bag<T>, 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<ValueId>,
/// 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<ValueId>,
}
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<ValueId, ValueInfo>,
blocks: Blocks<BlockId, ValueId, BlockInfo>,
}
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<ValueId>,
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<ValueId, ValueInfo> {
&self.values
}
pub(crate) fn blocks(&self) -> &Blocks<BlockId, ValueId, BlockInfo> {
&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<ValueId, ValueInfo>,
blocks: Blocks<BlockId, ValueId, BlockInfo>,
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<SccId, SccKind, BlockId>,
}
impl Graph {
pub(crate) fn values(&self) -> &OpGraph<ValueId, ValueInfo> {
&self.values
}
pub(crate) fn blocks(&self) -> &Blocks<BlockId, ValueId, BlockInfo> {
&self.blocks
}
pub(crate) fn entry_block(&self) -> BlockId {
self.entry_block
}
pub(crate) fn rev_sccs(&self) -> &FlatSlices<SccId, SccKind, BlockId> {
&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<Item = Slice<'_, SccKind, BlockId>> + '_ {
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,
}

View file

@ -1,10 +1,15 @@
#![allow(dead_code)] #![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
#[macro_use] #[macro_use]
mod util; mod util;
mod api; mod api;
mod bindings; mod bindings;
mod ir;
mod name_cache;
mod preprocess;
mod render_api_ir; mod render_api_ir;
mod type_cache;
pub use api::*; pub use api::*;

26
vendor/morphic_lib/src/name_cache.rs vendored Normal file
View file

@ -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<NamedTypeId, (api::ModNameBuf, api::TypeNameBuf)>,
pub(crate) funcs: IdBiMap<FuncId, (api::ModNameBuf, api::FuncNameBuf)>,
pub(crate) consts: IdBiMap<ConstId, (api::ModNameBuf, api::ConstNameBuf)>,
pub(crate) entry_points: IdBiMap<EntryPointId, api::EntryPointNameBuf>,
}

1204
vendor/morphic_lib/src/preprocess.rs vendored Normal file

File diff suppressed because it is too large Load diff

22
vendor/morphic_lib/src/type_cache.rs vendored Normal file
View file

@ -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<TypeId, TypeData>,
}

View file

@ -75,8 +75,8 @@ impl<BlockId: Id, ValId: Id, BlockInfo> Blocks<BlockId, ValId, BlockInfo> {
tail_frag.max_val = next_val; tail_frag.max_val = next_val;
} else { } else {
let new_tail = BlockFrag { let new_tail = BlockFrag {
min_val: val_id, min_val: val_id.clone(),
max_val: next_val, max_val: ValId::from_index_or_panic(val_id.to_index() + 1),
next: None, next: None,
}; };
let new_tail_id = self.frags.push(new_tail); let new_tail_id = self.frags.push(new_tail);

View file

@ -4,15 +4,15 @@ macro_rules! bytes_id {
$(#[$annot_borrowed:meta])* $borrowed_vis:vis $borrowed:ident; $(#[$annot_borrowed:meta])* $borrowed_vis:vis $borrowed:ident;
$(#[$annot_owned:meta])* $owned_vis:vis $owned: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])* $(#[$annot_borrowed])*
$borrowed_vis struct $borrowed<'a>($borrowed_vis &'a [u8]); $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 // On 64-bit platforms we can store up to `23` bytes inline in a `SmallVec` with no space
// 64-bit platforms we can make the array up to 16 bytes long with no space penalty. // penalty.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
$(#[$annot_owned])* $(#[$annot_owned])*
$owned_vis struct $owned($owned_vis ::smallvec::SmallVec<[u8; 16]>); $owned_vis struct $owned($owned_vis ::smallvec::SmallVec<[u8; 23]>);
impl $owned { impl $owned {
fn borrowed<'a>(&'a self) -> $borrowed<'a> { fn borrowed<'a>(&'a self) -> $borrowed<'a> {
@ -25,5 +25,27 @@ macro_rules! bytes_id {
$owned(::smallvec::SmallVec::from_slice(&borrowed.0)) $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())
}
}
} }
} }

View file

@ -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<SliceInfo> {
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<SliceId, (SliceInfo, Vec<T>)>`.
///
/// The notional `Vec<T>` 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<T>)` 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<SliceId: Id, SliceInfo, T> {
flat_data: Vec<T>,
slices: IdVec<SliceId, SliceData<SliceInfo>>,
}
#[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<SliceId: Id, SliceInfo, T> Default for FlatSlices<SliceId, SliceInfo, T> {
fn default() -> Self {
Self::new()
}
}
impl<SliceId: Id, SliceInfo, T> FlatSlices<SliceId, SliceInfo, T> {
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<SliceId> {
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<I: Borrow<SliceId>>(&self, idx: I) -> Slice<SliceInfo, T> {
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<I: Borrow<SliceId>>(&mut self, idx: I) -> SliceMut<SliceInfo, T> {
let (start, end) = self.data_range(idx.borrow().clone());
SliceMut {
info: &mut self.slices[idx].info,
items: &mut self.flat_data[start..end],
}
}
}
impl<SliceId: Id + fmt::Debug, SliceInfo: fmt::Debug, T: fmt::Debug> fmt::Debug
for FlatSlices<SliceId, SliceInfo, T>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.count().iter().map(|i| (i.clone(), self.get(i))))
.finish()
}
}

View file

@ -52,6 +52,18 @@ impl<K: Id, V: Hash + Eq + Clone> IdBiMap<K, V> {
} }
} }
/// 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<K> { pub fn get_by_val(&self, val: &V) -> Option<K> {
self.val_to_key.get(val).cloned() self.val_to_key.get(val).cloned()
} }

View file

@ -98,7 +98,14 @@ impl<T: PartialOrd> PartialOrd<T> for Count<T> {
} }
impl<T: Id> Count<T> { impl<T: Id> Count<T> {
pub fn iter(&self) -> impl Iterator<Item = T> { pub fn iter(&self) -> impl DoubleEndedIterator<Item = T> {
(0..self.0.to_index()).map(T::from_index_unchecked) (0..self.0.to_index()).map(T::from_index_unchecked)
} }
} }
pub fn decrement<T: Id>(id: T) -> Option<T> {
match id.to_index() {
0 => None,
index => Some(T::from_index_unchecked(index - 1)),
}
}

View file

@ -107,6 +107,13 @@ impl<K: Id, V> IdVec<K, V> {
} }
} }
pub fn with_capacity(capacity: usize) -> Self {
IdVec {
key: PhantomData,
items: Vec::with_capacity(capacity),
}
}
pub fn from_items(items: Vec<V>) -> Self { pub fn from_items(items: Vec<V>) -> Self {
K::assert_in_range(items.len()); K::assert_in_range(items.len());
IdVec { IdVec {

View file

@ -8,7 +8,9 @@ pub mod bytes_id;
pub mod forward_trait; pub mod forward_trait;
pub mod blocks; pub mod blocks;
pub mod flat_slices;
pub mod id_bi_map; pub mod id_bi_map;
pub mod id_vec; pub mod id_vec;
pub mod op_graph; pub mod op_graph;
pub mod replace_none; pub mod replace_none;
pub mod strongly_connected;

View file

@ -1,18 +1,7 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use crate::util::flat_slices::{FlatSlices, Slice};
use crate::util::id_type::{Count, Id}; 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: 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Node<'a, K, Op> { 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 /// The input lists are actually stored in a single contiguous buffer to reduce the number of heap
/// allocations. /// 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)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OpGraph<K: Id, Op> { pub struct OpGraph<K: Id, Op> {
flat_inputs: Vec<K>, inner: FlatSlices<K, Op, K>,
nodes: IdVec<K, NodeData<Op>>, }
impl<K: Id, Op> Default for OpGraph<K, Op> {
fn default() -> Self {
Self::new()
}
} }
impl<K: Id, Op> OpGraph<K, Op> { impl<K: Id, Op> OpGraph<K, Op> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
flat_inputs: Vec::new(), inner: FlatSlices::new(),
nodes: IdVec::new(),
} }
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.nodes.len() self.inner.len()
} }
pub fn count(&self) -> Count<K> { pub fn count(&self) -> Count<K> {
self.nodes.count() self.inner.count()
} }
pub fn add_node(&mut self, op: Op, inputs: &[K]) -> K { pub fn add_node(&mut self, op: Op, inputs: &[K]) -> K {
self.flat_inputs.extend_from_slice(inputs); self.inner.push_slice(op, inputs)
let node = NodeData {
op,
inputs_end_idx: self.flat_inputs.len(),
};
self.nodes.push(node)
} }
pub fn node<I: Borrow<K>>(&self, idx: I) -> Node<K, Op> { pub fn node<I: Borrow<K>>(&self, idx: I) -> Node<K, Op> {
let node = &self.nodes[idx.borrow()]; let Slice { info, items } = self.inner.get(idx);
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
};
Node { Node {
op: &node.op, op: info,
inputs: &self.flat_inputs[inputs_start_idx..node.inputs_end_idx], inputs: items,
} }
} }
} }

View file

@ -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<SccId, NodeId, NodeSuccessors>(
node_count: Count<NodeId>,
mut node_successors: impl FnMut(NodeId) -> NodeSuccessors,
) -> FlatSlices<SccId, SccKind, NodeId>
where
SccId: Id,
NodeId: Id + Eq + Copy,
NodeSuccessors: Iterator<Item = NodeId>,
{
// 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<NodeId> {
TryVisit {
parent: Option<NodeId>,
node: NodeId,
},
FinishVisit {
parent: Option<NodeId>,
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
}

77
vendor/morphic_lib/tests/basic.rs vendored Normal file
View file

@ -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);
}
}

95
vendor/morphic_lib/tests/recursive.rs vendored Normal file
View file

@ -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);
}
}