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 {
parameters,
continuation,
body: continuation,
remainder,
..
} => {

View file

@ -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,

View file

@ -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(

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";
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<Item = Layout<'a>>,
{
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<TypeI
fn str_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
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;

View file

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

View file

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

View file

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

View file

@ -64,7 +64,7 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>)
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() {

View file

@ -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<Layout<'a>>,
/// 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<Layout<'a>>, 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<Layout<'a>>,
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<Layout<'a>>, 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<Layout<'a>>,
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<Layout<'a>>, 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),
}
}

View file

@ -143,6 +143,14 @@ impl<'a> LambdaSet<'a> {
*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> {
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 {

View file

@ -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

View file

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

View file

@ -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<Solutions> {
preprocess::preprocess(&program).map_err(ErrorKind::PreprocessError)?;
Ok(Solutions {
mods: program
.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(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::*;

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

View file

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

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> {
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> {
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)
}
}
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 {
K::assert_in_range(items.len());
IdVec {

View file

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

View file

@ -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: 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<K: Id, Op> {
flat_inputs: Vec<K>,
nodes: IdVec<K, NodeData<Op>>,
inner: FlatSlices<K, Op, K>,
}
impl<K: Id, Op> Default for OpGraph<K, Op> {
fn default() -> Self {
Self::new()
}
}
impl<K: Id, Op> OpGraph<K, Op> {
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<K> {
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<I: Borrow<K>>(&self, idx: I) -> Node<K, Op> {
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,
}
}
}

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