Merge branch 'trunk' into document-only-exposed-values

This commit is contained in:
Chadtech 2021-06-13 13:59:51 -04:00 committed by GitHub
commit 64aba9ed46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1248 additions and 254 deletions

9
Cargo.lock generated
View file

@ -1763,9 +1763,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.95" version = "0.2.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" checksum = "5600b4e6efc5421841a2138a6b082e07fe12f9aaa12783d50e5d13325b26b4fc"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -2545,9 +2545,9 @@ dependencies = [
[[package]] [[package]]
name = "profiling" name = "profiling"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a66d5e88679f2720126c11ee29da07a08f094eac52306b066edd7d393752d6" checksum = "0a7c000c0ce9d9bb94c0fbacdf20e5087fbe652c556ffb2c9387d980e17d51fb"
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
@ -3219,6 +3219,7 @@ dependencies = [
"inlinable_string", "inlinable_string",
"libc", "libc",
"maplit", "maplit",
"morphic_lib",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
"quickcheck_macros 0.8.0", "quickcheck_macros 0.8.0",

View file

@ -2298,7 +2298,7 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
let make_err = tag( let make_err = tag(
"Err", "Err",
vec![tag("OutOfBounds", Vec::new(), var_store)], vec![tag("KeyNotFound", Vec::new(), var_store)],
var_store, var_store,
); );

View file

@ -60,7 +60,6 @@ pub enum Expr {
Float(Variable, Variable, f64), Float(Variable, Variable, f64),
Str(InlinableString), Str(InlinableString),
List { List {
list_var: Variable, // required for uniqueness of the list
elem_var: Variable, elem_var: Variable,
loc_elems: Vec<Located<Expr>>, loc_elems: Vec<Located<Expr>>,
}, },
@ -304,7 +303,6 @@ pub fn canonicalize_expr<'a>(
if loc_elems.is_empty() { if loc_elems.is_empty() {
( (
List { List {
list_var: var_store.fresh(),
elem_var: var_store.fresh(), elem_var: var_store.fresh(),
loc_elems: Vec::new(), loc_elems: Vec::new(),
}, },
@ -331,7 +329,6 @@ pub fn canonicalize_expr<'a>(
( (
List { List {
list_var: var_store.fresh(),
elem_var: var_store.fresh(), elem_var: var_store.fresh(),
loc_elems: can_elems, loc_elems: can_elems,
}, },
@ -1234,7 +1231,6 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ ForeignCall { .. } => other, | other @ ForeignCall { .. } => other,
List { List {
list_var,
elem_var, elem_var,
loc_elems, loc_elems,
} => { } => {
@ -1250,7 +1246,6 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
} }
List { List {
list_var,
elem_var, elem_var,
loc_elems: new_elems, loc_elems: new_elems,
} }

View file

@ -220,7 +220,6 @@ pub fn constrain_expr(
List { List {
elem_var, elem_var,
loc_elems, loc_elems,
list_var: _unused,
} => { } => {
if loc_elems.is_empty() { if loc_elems.is_empty() {
exists( exists(

View file

@ -16,6 +16,7 @@ roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
morphic_lib = { path = "../../vendor/morphic_lib" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }

View file

@ -42,6 +42,7 @@ use inkwell::values::{
}; };
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate}; use inkwell::{AddressSpace, IntPredicate};
use morphic_lib::{CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions};
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_module::ident::TagName; use roc_module::ident::TagName;
@ -583,22 +584,35 @@ pub fn construct_optimization_passes<'a>(
(mpm, fpm) (mpm, fpm)
} }
pub fn promote_to_main_function<'a, 'ctx, 'env>( fn promote_to_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, mod_solutions: &'a ModSolutions,
symbol: Symbol, symbol: Symbol,
layout: TopLevelFunctionLayout<'a>, top_level: TopLevelFunctionLayout<'a>,
) -> (&'static str, FunctionValue<'ctx>) { ) -> (&'static str, FunctionValue<'ctx>) {
let fn_name = layout_ids let it = top_level.arguments.iter().copied();
.get(symbol, &(env.arena.alloc(layout).full())) let bytes = roc_mono::alias_analysis::func_name_bytes_help(symbol, it, top_level.result);
.to_symbol_string(symbol, &env.interns); let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let roc_main_fn = env.module.get_function(&fn_name).unwrap(); let mut it = func_solutions.specs();
let func_spec = it.next().unwrap();
debug_assert!(
it.next().is_none(),
"we expect only one specialization of this symbol"
);
let roc_main_fn = function_value_by_func_spec(env, *func_spec, symbol, Layout::Struct(&[]));
let main_fn_name = "$Test.main"; let main_fn_name = "$Test.main";
// Add main to the module. // Add main to the module.
let main_fn = expose_function_to_host_help(env, roc_main_fn, main_fn_name); let main_fn = expose_function_to_host_help(
env,
&inlinable_string::InlinableString::from(main_fn_name),
roc_main_fn,
main_fn_name,
);
(main_fn_name, main_fn) (main_fn_name, main_fn)
} }
@ -772,6 +786,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
pub fn build_exp_call<'a, 'ctx, 'env>( pub fn build_exp_call<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
func_spec_solutions: &FuncSpecSolutions,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
@ -784,7 +799,10 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
match call_type { match call_type {
CallType::ByName { CallType::ByName {
name, full_layout, .. name,
full_layout,
specialization_id,
..
} => { } => {
let mut arg_tuples: Vec<BasicValueEnum> = let mut arg_tuples: Vec<BasicValueEnum> =
Vec::with_capacity_in(arguments.len(), env.arena); Vec::with_capacity_in(arguments.len(), env.arena);
@ -793,12 +811,15 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
arg_tuples.push(load_symbol(scope, symbol)); arg_tuples.push(load_symbol(scope, symbol));
} }
call_with_args( let bytes = specialization_id.to_bytes();
let callee_var = CalleeSpecVar(&bytes);
let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap();
roc_call_with_args(
env, env,
layout_ids,
&full_layout, &full_layout,
*name, *name,
parent, func_spec,
arg_tuples.into_bump_slice(), arg_tuples.into_bump_slice(),
) )
} }
@ -811,16 +832,26 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
op, op,
closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
} => run_higher_order_low_level( specialization_id,
env, ..
layout_ids, } => {
scope, let bytes = specialization_id.to_bytes();
layout, let callee_var = CalleeSpecVar(&bytes);
*op, let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap();
*closure_layout, // let fn_val = function_value_by_func_spec(env, func_spec, symbol, *layout);
*function_owns_closure_data,
arguments, run_higher_order_low_level(
), env,
layout_ids,
scope,
layout,
*op,
*closure_layout,
func_spec,
*function_owns_closure_data,
arguments,
)
}
CallType::Foreign { CallType::Foreign {
foreign_symbol, foreign_symbol,
@ -831,6 +862,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
build_foreign_symbol( build_foreign_symbol(
env, env,
layout_ids, layout_ids,
func_spec_solutions,
scope, scope,
parent, parent,
foreign_symbol, foreign_symbol,
@ -845,6 +877,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
pub fn build_exp_expr<'a, 'ctx, 'env>( pub fn build_exp_expr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
func_spec_solutions: &FuncSpecSolutions,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
@ -855,7 +888,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
match expr { match expr {
Literal(literal) => build_exp_literal(env, layout, literal), Literal(literal) => build_exp_literal(env, layout, literal),
Call(call) => build_exp_call(env, layout_ids, scope, parent, layout, call), Call(call) => build_exp_call(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
layout,
call,
),
Struct(sorted_fields) => { Struct(sorted_fields) => {
let ctx = env.context; let ctx = env.context;
@ -1802,6 +1843,7 @@ fn list_literal<'a, 'ctx, 'env>(
fn invoke_roc_function<'a, 'ctx, 'env>( fn invoke_roc_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
func_spec_solutions: &FuncSpecSolutions,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
symbol: Symbol, symbol: Symbol,
@ -1846,7 +1888,7 @@ fn invoke_roc_function<'a, 'ctx, 'env>(
scope.insert(symbol, (layout, call_result)); scope.insert(symbol, (layout, call_result));
build_exp_stmt(env, layout_ids, scope, parent, pass); build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, pass);
scope.remove(&symbol); scope.remove(&symbol);
} }
@ -1876,7 +1918,7 @@ fn invoke_roc_function<'a, 'ctx, 'env>(
(Layout::Struct(&[]), exception_object), (Layout::Struct(&[]), exception_object),
); );
build_exp_stmt(env, layout_ids, scope, parent, fail); build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, fail);
} }
call_result call_result
@ -1911,6 +1953,7 @@ fn decrement_with_size_check<'a, 'ctx, 'env>(
pub fn build_exp_stmt<'a, 'ctx, 'env>( pub fn build_exp_stmt<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
func_spec_solutions: &FuncSpecSolutions,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
stmt: &roc_mono::ir::Stmt<'a>, stmt: &roc_mono::ir::Stmt<'a>,
@ -1935,7 +1978,15 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
for (symbol, expr, layout) in queue { for (symbol, expr, layout) in queue {
debug_assert!(layout != &Layout::RecursivePointer); debug_assert!(layout != &Layout::RecursivePointer);
let val = build_exp_expr(env, layout_ids, scope, parent, layout, &expr); let val = build_exp_expr(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
layout,
&expr,
);
// Make a new scope which includes the binding we just encountered. // Make a new scope which includes the binding we just encountered.
// This should be done *after* compiling the bound expr, since any // This should be done *after* compiling the bound expr, since any
@ -1948,7 +1999,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
stack.push(*symbol); stack.push(*symbol);
} }
let result = build_exp_stmt(env, layout_ids, scope, parent, cont); let result = build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, cont);
for symbol in stack { for symbol in stack {
scope.remove(&symbol); scope.remove(&symbol);
@ -1979,7 +2030,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
// when the fail case is just Rethrow, there is no cleanup work to do // when the fail case is just Rethrow, there is no cleanup work to do
// so we can just treat this invoke as a normal call // so we can just treat this invoke as a normal call
let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass); let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
build_exp_stmt(env, layout_ids, scope, parent, &stmt) build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, &stmt)
} }
Invoke { Invoke {
@ -1993,13 +2044,20 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
CallType::ByName { CallType::ByName {
name, name,
ref full_layout, ref full_layout,
specialization_id,
.. ..
} => { } => {
let function_value = function_value_by_name(env, layout_ids, *full_layout, name); let bytes = specialization_id.to_bytes();
let callee_var = CalleeSpecVar(&bytes);
let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap();
let function_value =
function_value_by_func_spec(env, func_spec, name, *full_layout);
invoke_roc_function( invoke_roc_function(
env, env,
layout_ids, layout_ids,
func_spec_solutions,
scope, scope,
parent, parent,
*symbol, *symbol,
@ -2019,6 +2077,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
} => build_foreign_symbol( } => build_foreign_symbol(
env, env,
layout_ids, layout_ids,
func_spec_solutions,
scope, scope,
parent, parent,
foreign_symbol, foreign_symbol,
@ -2065,7 +2124,14 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
ret_type, ret_type,
}; };
build_switch_ir(env, layout_ids, scope, parent, switch_args) build_switch_ir(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
switch_args,
)
} }
Join { Join {
id, id,
@ -2096,7 +2162,14 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
scope.join_points.insert(*id, (cont_block, joinpoint_args)); scope.join_points.insert(*id, (cont_block, joinpoint_args));
// construct the blocks that may jump to this join point // construct the blocks that may jump to this join point
build_exp_stmt(env, layout_ids, scope, parent, remainder); build_exp_stmt(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
remainder,
);
let phi_block = builder.get_insert_block().unwrap(); let phi_block = builder.get_insert_block().unwrap();
@ -2109,7 +2182,14 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
} }
// put the continuation in // put the continuation in
let result = build_exp_stmt(env, layout_ids, scope, parent, continuation); let result = build_exp_stmt(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
continuation,
);
// remove this join point again // remove this join point again
scope.join_points.remove(&id); scope.join_points.remove(&id);
@ -2153,7 +2233,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
); );
} }
build_exp_stmt(env, layout_ids, scope, parent, cont) build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, cont)
} }
Dec(symbol) => { Dec(symbol) => {
let (value, layout) = load_symbol_and_layout(scope, symbol); let (value, layout) = load_symbol_and_layout(scope, symbol);
@ -2162,7 +2242,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
decrement_refcount_layout(env, parent, layout_ids, value, layout); decrement_refcount_layout(env, parent, layout_ids, value, layout);
} }
build_exp_stmt(env, layout_ids, scope, parent, cont) build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, cont)
} }
DecRef(symbol) => { DecRef(symbol) => {
let (value, layout) = load_symbol_and_layout(scope, symbol); let (value, layout) = load_symbol_and_layout(scope, symbol);
@ -2214,7 +2294,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
} }
} }
build_exp_stmt(env, layout_ids, scope, parent, cont) build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, cont)
} }
} }
} }
@ -2404,6 +2484,7 @@ fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValu
fn build_switch_ir<'a, 'ctx, 'env>( fn build_switch_ir<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
func_spec_solutions: &FuncSpecSolutions,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
switch_args: SwitchArgsIr<'a, 'ctx>, switch_args: SwitchArgsIr<'a, 'ctx>,
@ -2526,7 +2607,14 @@ fn build_switch_ir<'a, 'ctx, 'env>(
{ {
builder.position_at_end(then_block); builder.position_at_end(then_block);
let branch_val = build_exp_stmt(env, layout_ids, scope, parent, true_branch); let branch_val = build_exp_stmt(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
true_branch,
);
if then_block.get_terminator().is_none() { if then_block.get_terminator().is_none() {
builder.build_unconditional_branch(cont_block); builder.build_unconditional_branch(cont_block);
@ -2537,7 +2625,14 @@ fn build_switch_ir<'a, 'ctx, 'env>(
{ {
builder.position_at_end(else_block); builder.position_at_end(else_block);
let branch_val = build_exp_stmt(env, layout_ids, scope, parent, false_branch); let branch_val = build_exp_stmt(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
false_branch,
);
if else_block.get_terminator().is_none() { if else_block.get_terminator().is_none() {
builder.build_unconditional_branch(cont_block); builder.build_unconditional_branch(cont_block);
@ -2587,7 +2682,14 @@ fn build_switch_ir<'a, 'ctx, 'env>(
for ((_, _, branch_expr), (_, block)) in branches.iter().zip(cases) { for ((_, _, branch_expr), (_, block)) in branches.iter().zip(cases) {
builder.position_at_end(block); builder.position_at_end(block);
let branch_val = build_exp_stmt(env, layout_ids, scope, parent, branch_expr); let branch_val = build_exp_stmt(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
branch_expr,
);
if block.get_terminator().is_none() { if block.get_terminator().is_none() {
builder.build_unconditional_branch(cont_block); builder.build_unconditional_branch(cont_block);
@ -2598,7 +2700,14 @@ fn build_switch_ir<'a, 'ctx, 'env>(
// The block for the conditional's default branch. // The block for the conditional's default branch.
builder.position_at_end(default_block); builder.position_at_end(default_block);
let default_val = build_exp_stmt(env, layout_ids, scope, parent, default_branch); let default_val = build_exp_stmt(
env,
layout_ids,
func_spec_solutions,
scope,
parent,
default_branch,
);
if default_block.get_terminator().is_none() { if default_block.get_terminator().is_none() {
builder.build_unconditional_branch(cont_block); builder.build_unconditional_branch(cont_block);
@ -2646,16 +2755,19 @@ pub fn create_entry_block_alloca<'a, 'ctx>(
fn expose_function_to_host<'a, 'ctx, 'env>( fn expose_function_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
symbol: Symbol,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
) { ) {
let c_function_name: String = // Assumption: there is only one specialization of a host-exposed function
format!("roc_{}_exposed", roc_function.get_name().to_str().unwrap()); let ident_string = symbol.ident_string(&env.interns);
let c_function_name: String = format!("roc__{}_1_exposed", ident_string);
expose_function_to_host_help(env, roc_function, &c_function_name); expose_function_to_host_help(env, ident_string, roc_function, &c_function_name);
} }
fn expose_function_to_host_help<'a, 'ctx, 'env>( fn expose_function_to_host_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
ident_string: &inlinable_string::InlinableString,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
c_function_name: &str, c_function_name: &str,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
@ -2716,8 +2828,7 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
// STEP 3: build a {} -> u64 function that gives the size of the return type // STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false); let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = let size_function_name: String = format!("roc__{}_size", ident_string);
format!("roc_{}_size", roc_function.get_name().to_str().unwrap());
let size_function = add_func( let size_function = add_func(
env.module, env.module,
@ -2978,23 +3089,41 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
pub fn build_proc_headers<'a, 'ctx, 'env>( pub fn build_proc_headers<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, mod_solutions: &'a ModSolutions,
procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), roc_mono::ir::Proc<'a>>, procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), roc_mono::ir::Proc<'a>>,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
// alias_analysis_solutions: AliasAnalysisSolutions, // alias_analysis_solutions: AliasAnalysisSolutions,
) -> std::vec::Vec<(roc_mono::ir::Proc<'a>, FunctionValue<'ctx>)> { ) -> Vec<
'a,
(
roc_mono::ir::Proc<'a>,
&'a [(&'a FuncSpecSolutions, FunctionValue<'ctx>)],
),
> {
// Populate Procs further and get the low-level Expr from the canonical Expr // Populate Procs further and get the low-level Expr from the canonical Expr
let mut headers = std::vec::Vec::with_capacity(procedures.len()); let mut headers = Vec::with_capacity_in(procedures.len(), env.arena);
for ((symbol, layout), proc) in procedures { for ((symbol, layout), proc) in procedures {
let fn_val = build_proc_header(env, layout_ids, symbol, layout, &proc); let name_bytes = roc_mono::alias_analysis::func_name_bytes(&proc);
let func_name = FuncName(&name_bytes);
if proc.args.is_empty() { let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module! let it = func_solutions.specs();
scope.insert_top_level_thunk(symbol, env.arena.alloc(layout), fn_val); let mut function_values = Vec::with_capacity_in(it.size_hint().0, env.arena);
for specialization in it {
let fn_val = build_proc_header(env, *specialization, symbol, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, env.arena.alloc(layout), fn_val);
}
let func_spec_solutions = func_solutions.spec(specialization).unwrap();
function_values.push((func_spec_solutions, fn_val));
} }
headers.push((proc, function_values.into_bump_slice()));
headers.push((proc, fn_val));
} }
headers headers
@ -3026,18 +3155,6 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
.unwrap() .unwrap()
} }
// Coming soon
// pub enum AliasAnalysisSolutions {
// NotAvailable,
// Available(morphic_lib::Solutions),
// }
//
// impl std::fmt::Debug for AliasAnalysisSolutions {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "AliasAnalysisSolutions {{}}")
// }
// }
fn build_procedures_help<'a, 'ctx, 'env>( fn build_procedures_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
opt_level: OptLevel, opt_level: OptLevel,
@ -3047,82 +3164,107 @@ fn build_procedures_help<'a, 'ctx, 'env>(
let mut layout_ids = roc_mono::layout::LayoutIds::default(); let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut scope = Scope::default(); let mut scope = Scope::default();
// Coming Soon let it = procedures.iter().map(|x| x.1);
//
// if false { let solutions = match roc_mono::alias_analysis::spec_program(it) {
// let it = state.procedures.iter().map(|x| x.1); Err(e) => panic!("Error in alias analysis: {:?}", e),
// Ok(solutions) => solutions,
// match roc_mono::alias_analysis::spec_program(it) { };
// Err(e) => panic!("Error in alias analysis: {:?}", e),
// Ok(solutions) => { let solutions = env.arena.alloc(solutions);
// state.alias_analysis_solutions =
// AliasAnalysisSolutions::Available(solutions) let mod_solutions = solutions
// } .mod_solutions(roc_mono::alias_analysis::MOD_APP)
// } .unwrap();
// }
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
let headers = build_proc_headers(env, &mut layout_ids, procedures, &mut scope); let headers = build_proc_headers(env, &mod_solutions, procedures, &mut scope);
let (_, function_pass) = construct_optimization_passes(env.module, opt_level); let (_, function_pass) = construct_optimization_passes(env.module, opt_level);
for (proc, fn_val) in headers { for (proc, fn_vals) in headers {
let mut current_scope = scope.clone(); for (func_spec_solutions, fn_val) in fn_vals {
let mut current_scope = scope.clone();
// only have top-level thunks for this proc's module in scope // only have top-level thunks for this proc's module in scope
// this retain is not needed for correctness, but will cause less confusion when debugging // this retain is not needed for correctness, but will cause less confusion when debugging
let home = proc.name.module_id(); let home = proc.name.module_id();
current_scope.retain_top_level_thunks_for_module(home); current_scope.retain_top_level_thunks_for_module(home);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val); build_proc(
&env,
// call finalize() before any code generation/verification mod_solutions,
env.dibuilder.finalize(); &mut layout_ids,
func_spec_solutions,
if fn_val.verify(true) { scope.clone(),
function_pass.run_on(&fn_val); &proc,
} else { *fn_val,
let mode = "NON-OPTIMIZED";
eprintln!(
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap(),
mode,
); );
fn_val.print_to_stderr(); // call finalize() before any code generation/verification
// module.print_to_stderr(); env.dibuilder.finalize();
panic!( if fn_val.verify(true) {
"The preceding code was from {:?}, which failed LLVM verification in {} build.", function_pass.run_on(&fn_val);
fn_val.get_name().to_str().unwrap(), } else {
mode, let mode = "NON-OPTIMIZED";
);
eprintln!(
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap(),
mode,
);
fn_val.print_to_stderr();
// module.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap(),
mode,
);
}
} }
} }
main_data.map(|(main_fn_symbol, main_fn_layout)| { main_data.map(|(main_fn_symbol, main_fn_layout)| {
promote_to_main_function(env, &mut layout_ids, main_fn_symbol, main_fn_layout) promote_to_main_function(env, mod_solutions, main_fn_symbol, main_fn_layout)
}) })
} }
fn func_spec_name<'a>(
arena: &'a Bump,
interns: &Interns,
symbol: Symbol,
func_spec: FuncSpec,
) -> bumpalo::collections::String<'a> {
use std::fmt::Write;
let mut buf = bumpalo::collections::String::with_capacity_in(1, arena);
let ident_string = symbol.ident_string(interns);
let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap();
write!(buf, "{}_{}_", module_string, ident_string).unwrap();
for byte in func_spec.0.iter() {
write!(buf, "{:x?}", byte).unwrap();
}
buf
}
fn build_proc_header<'a, 'ctx, 'env>( fn build_proc_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, func_spec: FuncSpec,
symbol: Symbol, symbol: Symbol,
top_level: TopLevelFunctionLayout<'a>,
proc: &roc_mono::ir::Proc<'a>, proc: &roc_mono::ir::Proc<'a>,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let layout = env.arena.alloc(top_level).full();
let args = proc.args; let args = proc.args;
let arena = env.arena; let arena = env.arena;
let fn_name = layout_ids let fn_name = func_spec_name(env.arena, &env.interns, symbol, func_spec);
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let ret_type = basic_type_from_layout(env, &proc.ret_layout); let ret_type = basic_type_from_layout(env, &proc.ret_layout);
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
@ -3147,7 +3289,7 @@ fn build_proc_header<'a, 'ctx, 'env>(
fn_val.set_subprogram(subprogram); fn_val.set_subprogram(subprogram);
if env.exposed_to_host.contains(&symbol) { if env.exposed_to_host.contains(&symbol) {
expose_function_to_host(env, fn_val); expose_function_to_host(env, symbol, fn_val);
} }
fn_val fn_val
@ -3169,7 +3311,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
// e.g. `roc__main_1_Fx_caller` // e.g. `roc__main_1_Fx_caller`
let function_name = format!( let function_name = format!(
"roc_{}_{}_caller", "roc__{}_{}_caller",
def_name, def_name,
alias_symbol.ident_string(&env.interns) alias_symbol.ident_string(&env.interns)
); );
@ -3403,14 +3545,14 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
let size_function_type = env.context.i64_type().fn_type(&[], false); let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = if let Some(label) = opt_label { let size_function_name: String = if let Some(label) = opt_label {
format!( format!(
"roc_{}_{}_{}_size", "roc__{}_{}_{}_size",
def_name, def_name,
alias_symbol.ident_string(&env.interns), alias_symbol.ident_string(&env.interns),
label label
) )
} else { } else {
format!( format!(
"roc_{}_{}_size", "roc__{}_{}_size",
def_name, def_name,
alias_symbol.ident_string(&env.interns) alias_symbol.ident_string(&env.interns)
) )
@ -3434,9 +3576,11 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
pub fn build_proc<'a, 'ctx, 'env>( pub fn build_proc<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>, env: &'a Env<'a, 'ctx, 'env>,
mod_solutions: &'a ModSolutions,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
func_spec_solutions: &FuncSpecSolutions,
mut scope: Scope<'a, 'ctx>, mut scope: Scope<'a, 'ctx>,
proc: roc_mono::ir::Proc<'a>, proc: &roc_mono::ir::Proc<'a>,
fn_val: FunctionValue<'ctx>, fn_val: FunctionValue<'ctx>,
) { ) {
use roc_mono::ir::HostExposedLayouts; use roc_mono::ir::HostExposedLayouts;
@ -3454,16 +3598,28 @@ pub fn build_proc<'a, 'ctx, 'env>(
// * roc__mainForHost_1_Update_result_size() -> i64 // * roc__mainForHost_1_Update_result_size() -> i64
let evaluator_layout = env.arena.alloc(top_level).full(); let evaluator_layout = env.arena.alloc(top_level).full();
let evaluator_name = layout_ids
.get(symbol, &evaluator_layout)
.to_symbol_string(symbol, &env.interns);
let evaluator = function_value_by_name_help( let it = top_level.arguments.iter().copied();
env, let bytes = roc_mono::alias_analysis::func_name_bytes_help(
evaluator_layout,
symbol, symbol,
&evaluator_name, it,
top_level.result,
); );
let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs();
let func_spec = it.next().unwrap();
debug_assert!(
it.next().is_none(),
"we expect only one specialization of this symbol"
);
let evaluator =
function_value_by_func_spec(env, *func_spec, symbol, evaluator_layout);
let ident_string = proc.name.ident_string(&env.interns);
let fn_name: String = format!("{}_1", ident_string);
build_closure_caller( build_closure_caller(
env, &fn_name, evaluator, name, arguments, closure, result, env, &fn_name, evaluator, name, arguments, closure, result,
@ -3503,7 +3659,14 @@ pub fn build_proc<'a, 'ctx, 'env>(
scope.insert(*arg_symbol, (*layout, arg_val)); scope.insert(*arg_symbol, (*layout, arg_val));
} }
let body = build_exp_stmt(env, layout_ids, &mut scope, fn_val, &proc.body); let body = build_exp_stmt(
env,
layout_ids,
func_spec_solutions,
&mut scope,
fn_val,
&proc.body,
);
// only add a return if codegen did not already add one // only add a return if codegen did not already add one
if let Some(block) = builder.get_insert_block() { if let Some(block) = builder.get_insert_block() {
@ -3523,15 +3686,13 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
} }
} }
fn function_value_by_name<'a, 'ctx, 'env>( fn function_value_by_func_spec<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, func_spec: FuncSpec,
layout: Layout<'a>,
symbol: Symbol, symbol: Symbol,
layout: Layout<'a>,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let fn_name = layout_ids let fn_name = func_spec_name(env.arena, &env.interns, symbol, func_spec);
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = fn_name.as_str(); let fn_name = fn_name.as_str();
function_value_by_name_help(env, layout, symbol, fn_name) function_value_by_name_help(env, layout, symbol, fn_name)
@ -3573,15 +3734,14 @@ fn function_value_by_name_help<'a, 'ctx, 'env>(
// #[allow(clippy::cognitive_complexity)] // #[allow(clippy::cognitive_complexity)]
#[inline(always)] #[inline(always)]
fn call_with_args<'a, 'ctx, 'env>( fn roc_call_with_args<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>, layout: &Layout<'a>,
symbol: Symbol, symbol: Symbol,
_parent: FunctionValue<'ctx>, func_spec: FuncSpec,
args: &[BasicValueEnum<'ctx>], args: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let fn_val = function_value_by_name(env, layout_ids, *layout, symbol); let fn_val = function_value_by_func_spec(env, func_spec, symbol, *layout);
let call = env.builder.build_call(fn_val, args, "call"); let call = env.builder.build_call(fn_val, args, "call");
@ -3664,6 +3824,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
return_layout: &Layout<'a>, return_layout: &Layout<'a>,
op: LowLevel, op: LowLevel,
function_layout: Layout<'a>, function_layout: Layout<'a>,
func_spec: FuncSpec,
function_owns_closure_data: bool, function_owns_closure_data: bool,
args: &[Symbol], args: &[Symbol],
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
@ -3676,18 +3837,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
($index:expr) => {{ ($index:expr) => {{
let function_symbol = args[$index]; let function_symbol = args[$index];
let fn_name = layout_ids function_value_by_func_spec(env, func_spec, function_symbol, function_layout)
.get(function_symbol, &function_layout)
.to_symbol_string(function_symbol, &env.interns);
env.module
.get_function(fn_name.as_str())
.unwrap_or_else(|| {
panic!(
"Could not get pointer to unknown function {:?} {:?}",
fn_name, function_layout
)
})
}}; }};
} }
@ -4892,6 +5042,7 @@ fn build_foreign_symbol_write_result_into_ptr<'a, 'ctx, 'env>(
fn build_foreign_symbol<'a, 'ctx, 'env>( fn build_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
func_spec_solutions: &FuncSpecSolutions,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
foreign: &roc_module::ident::ForeignSymbol, foreign: &roc_module::ident::ForeignSymbol,
@ -4955,7 +5106,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
scope.insert(symbol, (*ret_layout, call_result)); scope.insert(symbol, (*ret_layout, call_result));
build_exp_stmt(env, layout_ids, scope, parent, pass); build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, pass);
scope.remove(&symbol); scope.remove(&symbol);
} }
@ -4987,7 +5138,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
(Layout::Struct(&[]), exception_object), (Layout::Struct(&[]), exception_object),
); );
build_exp_stmt(env, layout_ids, scope, parent, fail); build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, fail);
} }
call_result call_result

View file

@ -17,6 +17,65 @@ pub const MOD_APP: ModName = ModName(b"UserApp");
pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes()); pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes());
pub fn func_name_bytes(proc: &Proc) -> [u8; 16] {
func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), proc.ret_layout)
}
pub fn func_name_bytes_help<'a, I>(
symbol: Symbol,
argument_layouts: I,
return_layout: Layout<'a>,
) -> [u8; 16]
where
I: Iterator<Item = Layout<'a>>,
{
let mut name_bytes = [0u8; 16];
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
let layout_hash = {
let mut hasher = DefaultHasher::new();
for layout in argument_layouts {
match layout {
Layout::Closure(_, lambda_set, _) => {
lambda_set.runtime_representation().hash(&mut hasher);
}
_ => {
layout.hash(&mut hasher);
}
}
}
match return_layout {
Layout::Closure(_, lambda_set, _) => {
lambda_set.runtime_representation().hash(&mut hasher);
}
_ => {
return_layout.hash(&mut hasher);
}
}
hasher.finish()
};
let sbytes = symbol.to_ne_bytes();
let lbytes = layout_hash.to_ne_bytes();
let it = sbytes
.iter()
.chain(lbytes.iter())
.zip(name_bytes.iter_mut());
for (source, target) in it {
*target = *source;
}
name_bytes
}
pub fn spec_program<'a, I>(procs: I) -> Result<morphic_lib::Solutions> pub fn spec_program<'a, I>(procs: I) -> Result<morphic_lib::Solutions>
where where
I: Iterator<Item = &'a Proc<'a>>, I: Iterator<Item = &'a Proc<'a>>,
@ -40,36 +99,7 @@ where
for proc in procs { for proc in procs {
let spec = proc_spec(proc)?; let spec = proc_spec(proc)?;
let mut name_bytes = [0u8; 16]; m.add_func(FuncName(&func_name_bytes(proc)), spec)?;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
let layout_hash = {
let mut hasher = DefaultHasher::new();
for (layout, _) in proc.args.iter() {
layout.hash(&mut hasher);
}
proc.ret_layout.hash(&mut hasher);
hasher.finish()
};
let sbytes = proc.name.to_ne_bytes();
let lbytes = layout_hash.to_ne_bytes();
let it = sbytes
.iter()
.chain(lbytes.iter())
.zip(name_bytes.iter_mut());
for (source, target) in it {
*target = *source;
}
m.add_func(FuncName(&name_bytes), spec)?;
if format!("{:?}", proc.name).contains("mainForHost") { if format!("{:?}", proc.name).contains("mainForHost") {
main_function = Some(proc.name); main_function = Some(proc.name);
@ -112,6 +142,8 @@ where
p.build()? p.build()?
}; };
// eprintln!("{}", program.to_source_string());
morphic_lib::solve(program) morphic_lib::solve(program)
} }
} }
@ -345,16 +377,17 @@ fn call_spec(
ByName { ByName {
name: symbol, name: symbol,
full_layout: _, full_layout: _,
ret_layout: _, ret_layout,
arg_layouts: _, arg_layouts,
specialization_id, specialization_id,
} => { } => {
let array = specialization_id.to_bytes(); let array = specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array); let spec_var = CalleeSpecVar(&array);
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?; let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
let slice = &symbol.to_ne_bytes(); let it = arg_layouts.iter().copied();
let name = FuncName(slice); let bytes = func_name_bytes_help(*symbol, it, *ret_layout);
let name = FuncName(&bytes);
let module = MOD_APP; let module = MOD_APP;
builder.add_call(block, spec_var, module, name, arg_value_id) builder.add_call(block, spec_var, module, name, arg_value_id)
} }
@ -381,7 +414,64 @@ fn call_spec(
*update_mode, *update_mode,
call.arguments, call.arguments,
), ),
HigherOrderLowLevel { .. } => { HigherOrderLowLevel {
specialization_id,
closure_layout: _,
op,
arg_layouts,
ret_layout,
..
} => {
let array = specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array);
let symbol = {
use roc_module::low_level::LowLevel::*;
match op {
ListMap | ListMapWithIndex => call.arguments[1],
ListMap2 => call.arguments[2],
ListMap3 => call.arguments[3],
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => call.arguments[2],
ListKeepIf | ListKeepOks | ListKeepErrs => call.arguments[1],
ListSortWith => call.arguments[1],
_ => unreachable!(),
}
};
let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(symbol, it, *ret_layout);
let name = FuncName(&bytes);
let module = MOD_APP;
{
use roc_module::low_level::LowLevel::*;
match op {
DictWalk => {
let dict = env.symbols[&call.arguments[0]];
let default = env.symbols[&call.arguments[1]];
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])?;
builder.add_call(block, spec_var, module, name, argument)?;
}
_ => {
// fake a call to the function argument
// to make sure the function is specialized
// very invalid
let arg_value_id = build_tuple_value(builder, env, block, &[])?;
builder.add_call(block, spec_var, module, name, arg_value_id)?;
}
}
}
// TODO overly pessimstic // TODO overly pessimstic
// filter_map because one of the arguments is a function name, which // filter_map because one of the arguments is a function name, which
// is not defined in the env // is not defined in the env
@ -740,6 +830,9 @@ fn str_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
const LIST_CELL_INDEX: u32 = 0; const LIST_CELL_INDEX: u32 = 0;
const LIST_BAG_INDEX: u32 = 1; const LIST_BAG_INDEX: u32 = 1;
const DICT_CELL_INDEX: u32 = LIST_CELL_INDEX;
const DICT_BAG_INDEX: u32 = LIST_BAG_INDEX;
fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result<ValueId> { fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result<ValueId> {
let cell = builder.add_new_heap_cell(block)?; let cell = builder.add_new_heap_cell(block)?;
let bag = builder.add_empty_bag(block, element_type)?; let bag = builder.add_empty_bag(block, element_type)?;

View file

@ -454,7 +454,12 @@ impl<'a> Context<'a> {
} }
HigherOrderLowLevel { HigherOrderLowLevel {
op, closure_layout, .. op,
closure_layout,
specialization_id,
arg_layouts,
ret_layout,
..
} => { } => {
macro_rules! create_call { macro_rules! create_call {
($borrows:expr) => { ($borrows:expr) => {
@ -464,6 +469,9 @@ impl<'a> Context<'a> {
op: *op, op: *op,
closure_layout: *closure_layout, closure_layout: *closure_layout,
function_owns_closure_data: true, function_owns_closure_data: true,
specialization_id: *specialization_id,
arg_layouts,
ret_layout: *ret_layout,
} }
} else { } else {
call_type call_type

View file

@ -1146,8 +1146,13 @@ pub enum CallType<'a> {
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_layout: Layout<'a>,
/// specialization id of the function argument
specialization_id: CallSpecId,
/// does the function need to own the closure data /// does the function need to own the closure data
function_owns_closure_data: bool, function_owns_closure_data: bool,
/// function layout
arg_layouts: &'a [Layout<'a>],
ret_layout: Layout<'a>,
}, },
} }
@ -2575,8 +2580,8 @@ fn cleanup_attempted_type<'a>(
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TopLevelFunctionLayout<'a> { pub struct TopLevelFunctionLayout<'a> {
arguments: &'a [Layout<'a>], pub arguments: &'a [Layout<'a>],
result: Layout<'a>, pub result: Layout<'a>,
} }
impl<'a> TopLevelFunctionLayout<'a> { impl<'a> TopLevelFunctionLayout<'a> {
@ -2701,21 +2706,29 @@ 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 ret_layout = top_level.result;
match closure_data_layout { match closure_data_layout {
Layout::Closure(_, lambda_set, _) => { Layout::Closure(_, lambda_set, _) => {
lowlevel_match_on_lambda_set( lowlevel_match_on_lambda_set(
$env, $env,
lambda_set, lambda_set,
$closure_data_symbol, $closure_data_symbol,
|top_level_function, closure_data, function_layout| self::Call { |top_level_function, closure_data, function_layout, specialization_id| self::Call {
call_type: CallType::HigherOrderLowLevel { call_type: CallType::HigherOrderLowLevel {
op: $op, op: $op,
closure_layout: function_layout, closure_layout: function_layout,
function_owns_closure_data: false specialization_id,
function_owns_closure_data: false,
arg_layouts,
ret_layout,
}, },
arguments: arena.alloc([$($x,)* top_level_function, closure_data]), arguments: arena.alloc([$($x,)* top_level_function, closure_data]),
}, },
arena.alloc(top_level).full(), function_layout,
$layout, $layout,
$assigned, $assigned,
$hole, $hole,
@ -3393,7 +3406,6 @@ pub fn with_hole<'a>(
} }
List { List {
list_var: _,
elem_var, elem_var,
loc_elems, loc_elems,
} => { } => {
@ -7696,7 +7708,7 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> ) -> Stmt<'a>
where where
ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>) -> Call<'a> + Copy, ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy,
{ {
match lambda_set.runtime_representation() { match lambda_set.runtime_representation() {
Layout::Union(_) => { Layout::Union(_) => {
@ -7733,7 +7745,13 @@ where
Layout::Struct(_) => { Layout::Struct(_) => {
let function_symbol = lambda_set.set[0].0; let function_symbol = lambda_set.set[0].0;
let call = to_lowlevel_call(function_symbol, closure_data_symbol, function_layout); let call_spec_id = env.next_call_specialization_id();
let call = to_lowlevel_call(
function_symbol,
closure_data_symbol,
function_layout,
call_spec_id,
);
build_call(env, call, assigned, return_layout, env.arena.alloc(hole)) build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
} }
@ -7787,7 +7805,7 @@ fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>(
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> ) -> Stmt<'a>
where where
ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>) -> Call<'a> + Copy, ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy,
{ {
debug_assert!(!lambda_set.is_empty()); debug_assert!(!lambda_set.is_empty());
@ -7800,7 +7818,13 @@ where
let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned])); let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned]));
let call = to_lowlevel_call(*function_symbol, closure_data_symbol, function_layout); let call_spec_id = env.next_call_specialization_id();
let call = to_lowlevel_call(
*function_symbol,
closure_data_symbol,
function_layout,
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));
branches.push((i as u64, BranchInfo::None, stmt)); branches.push((i as u64, BranchInfo::None, stmt));
@ -8206,7 +8230,7 @@ fn lowlevel_enum_lambda_set_to_switch<'a, ToLowLevelCall>(
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> ) -> Stmt<'a>
where where
ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>) -> Call<'a> + Copy, ToLowLevelCall: Fn(Symbol, Symbol, Layout<'a>, CallSpecId) -> Call<'a> + Copy,
{ {
debug_assert!(!lambda_set.is_empty()); debug_assert!(!lambda_set.is_empty());
@ -8219,7 +8243,13 @@ where
let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol])); let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
let call = to_lowlevel_call(*function_symbol, closure_data_symbol, function_layout); let call_spec_id = env.next_call_specialization_id();
let call = to_lowlevel_call(
*function_symbol,
closure_data_symbol,
function_layout,
call_spec_id,
);
let stmt = build_call( let stmt = build_call(
env, env,
call, call,

View file

@ -43,6 +43,7 @@ pub enum Layout<'a> {
Struct(&'a [Layout<'a>]), Struct(&'a [Layout<'a>]),
Union(UnionLayout<'a>), Union(UnionLayout<'a>),
RecursivePointer, RecursivePointer,
/// A function. The types of its arguments, then the type of its return value. /// A function. The types of its arguments, then the type of its return value.
FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>), FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>),
Closure(&'a [Layout<'a>], LambdaSet<'a>, &'a Layout<'a>), Closure(&'a [Layout<'a>], LambdaSet<'a>, &'a Layout<'a>),

View file

@ -114,7 +114,6 @@ pub enum Expr2 {
InvalidLookup(PoolStr), // 8B InvalidLookup(PoolStr), // 8B
List { List {
list_var: Variable, // 4B - required for uniqueness of the list
elem_var: Variable, // 4B elem_var: Variable, // 4B
elems: PoolVec<Expr2>, // 8B elems: PoolVec<Expr2>, // 8B
}, },

View file

@ -346,7 +346,6 @@ pub fn to_expr2<'a>(
} }
let expr = Expr2::List { let expr = Expr2::List {
list_var: env.var_store.fresh(),
elem_var: env.var_store.fresh(), elem_var: env.var_store.fresh(),
elems, elems,
}; };

View file

@ -7,9 +7,9 @@ app "closure"
main : Task.Task {} [] main : Task.Task {} []
main = closure1 {} main = closure1 {}
|> Task.after (\_ -> closure2 {}) # |> Task.after (\_ -> closure2 {})
|> Task.after (\_ -> closure3 {}) # |> Task.after (\_ -> closure3 {})
|> Task.after (\_ -> closure4 {}) # |> Task.after (\_ -> closure4 {})
# --- # ---

View file

@ -24,7 +24,7 @@ const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed([*]u8) void; extern fn roc__mainForHost_1_exposed([*]u8) void;
extern fn roc__mainForHost_1_size() i64; extern fn roc__mainForHost_size() i64;
extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_1_Fx_size() i64; extern fn roc__mainForHost_1_Fx_size() i64;
extern fn roc__mainForHost_1_Fx_result_size() i64; extern fn roc__mainForHost_1_Fx_result_size() i64;
@ -51,7 +51,7 @@ pub export fn main() u8 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
const size = @intCast(usize, roc__mainForHost_1_size()); const size = @intCast(usize, roc__mainForHost_size());
const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);

View file

@ -0,0 +1,76 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
void* roc_alloc(size_t size, unsigned int alignment) {
return malloc(size);
}
void* roc_realloc(void* ptr, size_t old_size, size_t new_size, unsigned int alignment) {
return realloc(ptr, new_size);
}
void roc_dealloc(void* ptr, unsigned int alignment) {
free(ptr);
}
struct RocStr {
char* bytes;
size_t len;
};
struct RocCallResult {
size_t flag;
struct RocStr content;
};
extern void roc__mainForHost_1_exposed(struct RocCallResult *re);
const size_t MAX_STACK_STR_BYTES = 1024;
int main() {
// make space for the result
struct RocCallResult callresult;
// call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult);
struct RocStr str = callresult.content;
// Convert from RocStr to C string (null-terminated char*)
size_t len = str.len;
char* c_str;
// Allocate on the stack unless the string is particularly big.
// (Don't want a stack overflow!)
if (len <= MAX_STACK_STR_BYTES) {
c_str = (char*)alloca(len + 1);
} else {
c_str = (char*)malloc(len + 1);
}
memcpy(c_str, str.bytes, len);
// null-terminate
c_str[len] = 0;
// Print the string to stdout
printf("%s\n", c_str);
// Pointer to the beginning of the RocStr's actual allocation, which is
// the size_t immediately preceding the first stored byte.
size_t* str_base_ptr = (size_t*)str.bytes - 1;
// If *str_base_ptr is equal to 0, then the string is in the
// read-only data section of the binary, and can't be freed!
if (*str_base_ptr != 0) {
roc_dealloc(str_base_ptr, 8);
}
// If we malloc'd c_str, free it.
if (len > MAX_STACK_STR_BYTES) {
free(c_str);
}
return 0;
}

1
examples/hello-zig/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
hello-world

View file

@ -0,0 +1,12 @@
app "hello-world"
packages { base: "platform" }
imports []
provides [ main ] to base
greeting =
hi = "Hello"
name = "World"
"\(hi), \(name)!!!!!!!!!!!!!"
main = greeting

View file

@ -0,0 +1,48 @@
# Hello, World!
To run, `cd` into this directory and run:
```bash
$ cargo run run Hello.roc
```
To run in release mode instead, do:
```bash
$ cargo run --release run Hello.roc
```
## Troubleshooting
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.
## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure
function (in this case, a thunk that always returns `"Hello, World!"`) and
then the host calls that function. Fundamentally, that's the whole idea! The host
might not even have a `main` - it could be a library, a plugin, anything.
Everything else is built on this basic "hosts calling linked pure functions" design.
For example, things get more interesting when the compiled Roc function returns
a `Task` - that is, a tagged union data structure containing function pointers
to callback closures. This lets the Roc pure function describe arbitrary
chainable effects, which the host can interpret to perform I/O as requested by
the Roc program. (The tagged union `Task` would have a variant for each supported
I/O operation.)
In this trivial example, it's very easy to line up the API between the host and
the Roc program. In a more involved host, this would be much trickier - especially
if the API were changing frequently during development.
The idea there is to have a first-class concept of "glue code" which host authors
can write (it would be plain Roc code, but with some extra keywords that aren't
available in normal modules - kinda like `port module` in Elm), and which
describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary.
Roc application authors only care about the Roc-host/Roc-app portion, and the
host author only cares about the Roc-host/C bounary when implementing the host.
Using this glue code, the Roc compiler can generate C header files describing the
boundary. This not only gets us host compatibility with C compilers, but also
Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen)
generates correct Rust FFI bindings from C headers.

View file

@ -0,0 +1,10 @@
platform examples/hello-world
requires {}{ main : Str }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -6,5 +6,7 @@ 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

@ -1,7 +1,8 @@
use sha2::{digest::Digest, Sha256}; use sha2::{digest::Digest, Sha256};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::collections::{btree_map::Entry, BTreeMap, BTreeSet}; use std::collections::{btree_map::Entry, BTreeMap};
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;
use crate::util::id_type::Count; use crate::util::id_type::Count;
@ -554,11 +555,14 @@ pub(crate) enum TypeOp {
} }
/// A `TypeDef` defines the content type of a named type. /// A `TypeDef` defines the content type of a named type.
pub struct TypeDef; pub struct TypeDef {
pub(crate) builder: TypeBuilder,
pub(crate) root: TypeId,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct TypeBuilder { pub(crate) struct TypeBuilder {
types: OpGraph<TypeId, TypeOp>, pub(crate) types: OpGraph<TypeId, TypeOp>,
} }
impl Default for TypeBuilder { impl Default for TypeBuilder {
@ -637,7 +641,10 @@ impl TypeDefBuilder {
/// Create a `TypeDef` using the given type node as the root. /// Create a `TypeDef` using the given type node as the root.
pub fn build(self, root: TypeId) -> Result<TypeDef> { pub fn build(self, root: TypeId) -> Result<TypeDef> {
self.inner.check(root)?; self.inner.check(root)?;
Ok(TypeDef) Ok(TypeDef {
builder: self.inner,
root,
})
} }
} }
@ -743,27 +750,27 @@ pub(crate) enum Op {
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct ContinuationInfo { pub(crate) struct ContinuationInfo {
arg_type: TypeId, pub(crate) arg_type: TypeId,
ret_type: TypeId, pub(crate) ret_type: TypeId,
arg: ValueId, pub(crate) arg: ValueId,
body: Option<BlockExpr>, pub(crate) body: Option<BlockExpr>,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
enum BlockState { pub(crate) enum BlockState {
Detached, Detached,
Attached, Attached,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct ExprBuilder { pub(crate) struct ExprBuilder {
type_builder: TypeBuilder, pub(crate) type_builder: TypeBuilder,
blocks: Blocks<BlockId, ValueId, BlockState>, pub(crate) blocks: Blocks<BlockId, ValueId, BlockState>,
vals: OpGraph<ValueId, Op>, pub(crate) vals: OpGraph<ValueId, Op>,
continuations: IdVec<ContinuationId, ContinuationInfo>, pub(crate) continuations: IdVec<ContinuationId, ContinuationInfo>,
callee_spec_vars: IdBiMap<CalleeSpecVarId, CalleeSpecBuf>, pub(crate) callee_spec_vars: IdBiMap<CalleeSpecVarId, CalleeSpecBuf>,
update_mode_vars: IdBiMap<UpdateModeVarId, UpdateModeBuf>, pub(crate) update_mode_vars: IdBiMap<UpdateModeVarId, UpdateModeBuf>,
} }
impl Default for ExprBuilder { impl Default for ExprBuilder {
@ -1134,15 +1141,15 @@ impl ExprContext for ExprBuilder {
/// A `FuncDef` defines the signature and body of a function. /// A `FuncDef` defines the signature and body of a function.
pub struct FuncDef { pub struct FuncDef {
builder: FuncDefBuilder, pub(crate) builder: FuncDefBuilder,
arg_type: TypeId, pub(crate) arg_type: TypeId,
ret_type: TypeId, pub(crate) ret_type: TypeId,
root: BlockExpr, pub(crate) root: BlockExpr,
} }
pub struct FuncDefBuilder { pub struct FuncDefBuilder {
expr_builder: ExprBuilder, pub(crate) expr_builder: ExprBuilder,
argument: ValueId, pub(crate) argument: ValueId,
} }
impl Default for FuncDefBuilder { impl Default for FuncDefBuilder {
@ -1187,13 +1194,13 @@ impl FuncDefBuilder {
/// A `ConstDef` defines the type and initializer expression for a global constant. /// A `ConstDef` defines the type and initializer expression for a global constant.
pub struct ConstDef { pub struct ConstDef {
builder: ConstDefBuilder, pub(crate) builder: ConstDefBuilder,
type_: TypeId, pub(crate) type_: TypeId,
root: BlockExpr, pub(crate) root: BlockExpr,
} }
pub struct ConstDefBuilder { pub struct ConstDefBuilder {
expr_builder: ExprBuilder, pub(crate) expr_builder: ExprBuilder,
} }
impl Default for ConstDefBuilder { impl Default for ConstDefBuilder {
@ -1225,12 +1232,13 @@ impl ConstDefBuilder {
/// A `ModDef` defines a module, which is a collection of named types, functions, and constants. /// A `ModDef` defines a module, which is a collection of named types, functions, and constants.
pub struct ModDef { pub struct ModDef {
func_defs: BTreeMap<FuncNameBuf, FuncDef>, pub(crate) type_defs: BTreeMap<TypeNameBuf, TypeDef>,
const_defs: BTreeMap<ConstNameBuf, ConstDef>, pub(crate) func_defs: BTreeMap<FuncNameBuf, FuncDef>,
pub(crate) const_defs: BTreeMap<ConstNameBuf, ConstDef>,
} }
pub struct ModDefBuilder { pub struct ModDefBuilder {
type_names: BTreeSet<TypeNameBuf>, type_defs: BTreeMap<TypeNameBuf, TypeDef>,
func_defs: BTreeMap<FuncNameBuf, FuncDef>, func_defs: BTreeMap<FuncNameBuf, FuncDef>,
const_defs: BTreeMap<ConstNameBuf, ConstDef>, const_defs: BTreeMap<ConstNameBuf, ConstDef>,
} }
@ -1244,7 +1252,7 @@ impl Default for ModDefBuilder {
impl ModDefBuilder { impl ModDefBuilder {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
type_names: BTreeSet::new(), type_defs: BTreeMap::new(),
func_defs: BTreeMap::new(), func_defs: BTreeMap::new(),
const_defs: BTreeMap::new(), const_defs: BTreeMap::new(),
} }
@ -1252,13 +1260,14 @@ impl ModDefBuilder {
pub fn build(self) -> Result<ModDef> { pub fn build(self) -> Result<ModDef> {
Ok(ModDef { Ok(ModDef {
type_defs: self.type_defs,
func_defs: self.func_defs, func_defs: self.func_defs,
const_defs: self.const_defs, const_defs: self.const_defs,
}) })
} }
pub fn add_named_type(&mut self, name: TypeName, _type_def: TypeDef) -> Result<()> { pub fn add_named_type(&mut self, name: TypeName, type_def: TypeDef) -> Result<()> {
if !self.type_names.insert(name.into()) { if self.type_defs.insert(name.into(), type_def).is_some() {
return Err(ErrorKind::DuplicateTypeName(name.into()).into()); return Err(ErrorKind::DuplicateTypeName(name.into()).into());
} }
Ok(()) Ok(())
@ -1284,8 +1293,14 @@ impl ModDefBuilder {
/// ///
/// Each entry point has an associated main function, which must have signature `() -> ()`. /// Each entry point has an associated main function, which must have signature `() -> ()`.
pub struct Program { pub struct Program {
mods: BTreeMap<ModNameBuf, ModDef>, pub(crate) mods: BTreeMap<ModNameBuf, ModDef>,
entry_points: BTreeMap<EntryPointNameBuf, (ModNameBuf, FuncNameBuf)>, pub(crate) entry_points: BTreeMap<EntryPointNameBuf, (ModNameBuf, FuncNameBuf)>,
}
impl Program {
pub fn to_source_string(&self) -> String {
render_api_ir::render_program_to_string(self)
}
} }
pub struct ProgramBuilder { pub struct ProgramBuilder {

View file

@ -1,6 +1,8 @@
// TODO: These bindings are incomplete // TODO: These bindings are incomplete
// TODO: Add test for compatibility with `include/morphic.h` // TODO: Add test for compatibility with `include/morphic.h`
#![allow(clippy::boxed_local)]
use crate::api::*; use crate::api::*;
use std::{ffi::CString, os::raw::c_char, ptr, slice}; use std::{ffi::CString, os::raw::c_char, ptr, slice};

View file

@ -5,5 +5,6 @@ mod util;
mod api; mod api;
mod bindings; mod bindings;
mod render_api_ir;
pub use api::*; pub use api::*;

550
vendor/morphic_lib/src/render_api_ir.rs vendored Normal file
View file

@ -0,0 +1,550 @@
use std::fmt::{Display, Formatter, Write};
use crate::api::{
BlockExpr, CalleeSpecBuf, ConstDef, ConstNameBuf, ContinuationId, EntryPointNameBuf,
ExprBuilder, FuncDef, FuncNameBuf, ModDef, ModNameBuf, Op, Program, TypeDef, TypeId,
TypeNameBuf, TypeOp, UpdateModeBuf, ValueId,
};
use crate::util::id_type::Id;
use crate::util::op_graph::{Node, OpGraph};
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct RenderContext {
content: String,
spaces_per_level: usize,
indent_level: usize,
pending_indent: bool,
}
impl RenderContext {
fn new(spaces_per_level: usize) -> RenderContext {
RenderContext {
content: String::new(),
spaces_per_level,
indent_level: 0,
pending_indent: true,
}
}
fn with_indent<R>(&mut self, body: impl for<'a> FnOnce(&'a mut RenderContext) -> R) -> R {
self.indent_level += 1;
let result = body(self);
self.indent_level -= 1;
result
}
fn write(&mut self, to_write: impl std::fmt::Display) {
if self.pending_indent {
self.content
.extend((0..self.indent_level * self.spaces_per_level).map(|_| ' '));
self.pending_indent = false;
}
write!(&mut self.content, "{}", to_write).expect("writing to string failed");
}
fn writeln(&mut self, to_write: impl std::fmt::Display) {
self.write(to_write);
self.content.push('\n');
self.pending_indent = true;
}
fn finish(self) -> String {
self.content
}
}
#[derive(Clone, Copy, Debug)]
struct Ident {
prefix: &'static str,
id: usize,
}
impl Display for Ident {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}_{}", self.prefix, self.id)
}
}
fn val_ident(value: ValueId) -> Ident {
Ident {
prefix: "val",
id: value.to_index(),
}
}
fn type_ident(type_: TypeId) -> Ident {
Ident {
prefix: "type",
id: type_.to_index(),
}
}
fn continuation_ident(continuation: ContinuationId) -> Ident {
Ident {
prefix: "cont",
id: continuation.to_index(),
}
}
#[derive(Clone, Copy, Debug)]
struct ByteString<'a>(&'a [u8]);
impl<'a> Display for ByteString<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "\"")?;
for &byte in self.0 {
write!(f, "{}", std::ascii::escape_default(byte))?;
}
write!(f, "\"")?;
Ok(())
}
}
fn update_mode_string(buf: &UpdateModeBuf) -> ByteString {
ByteString(&buf.0)
}
fn callee_spec_string(buf: &CalleeSpecBuf) -> ByteString {
ByteString(&buf.0)
}
fn mod_name_string(mod_name: &ModNameBuf) -> ByteString {
ByteString(&mod_name.0)
}
fn func_name_string(func_name: &FuncNameBuf) -> ByteString {
ByteString(&func_name.0)
}
fn type_name_string(type_name: &TypeNameBuf) -> ByteString {
ByteString(&type_name.0)
}
fn const_name_string(const_name: &ConstNameBuf) -> ByteString {
ByteString(&const_name.0)
}
fn entry_point_name_string(entry_point: &EntryPointNameBuf) -> ByteString {
ByteString(&entry_point.0)
}
fn delimit<T>(
ctx: &mut RenderContext,
items: impl Iterator<Item = T>,
mut render_delim: impl for<'a> FnMut(&'a mut RenderContext),
mut render_item: impl for<'a> FnMut(&'a mut RenderContext, T),
) {
for (i, item) in items.enumerate() {
if i != 0 {
render_delim(ctx);
}
render_item(ctx, item);
}
}
fn render_op(builder: &ExprBuilder, ctx: &mut RenderContext, op: &Op) {
match op {
Op::Arg | Op::ContinuationArg | Op::DeclareContinuation { .. } => {
ctx.write(format_args!(
"/* internal error: {:?} should not be rendered as a value */",
op
));
}
Op::Jump {
continuation,
unreachable_result_type,
} => {
ctx.write(format_args!(
"jump<{}> {}",
type_ident(*unreachable_result_type),
continuation_ident(*continuation),
));
}
Op::UnknownWith { result_type } => {
ctx.write(format_args!("unknown_with<{}>", type_ident(*result_type)));
}
Op::Call {
callee_spec_var,
callee_mod,
callee,
} => {
ctx.write(format_args!(
"call[{}] {}::{}",
callee_spec_string(&builder.callee_spec_vars[callee_spec_var]),
mod_name_string(callee_mod),
func_name_string(callee),
));
}
Op::ConstRef { const_mod, const_ } => {
ctx.write(format_args!(
"const_ref {}::{}",
mod_name_string(const_mod),
const_name_string(const_),
));
}
Op::Choice { cases } => {
ctx.writeln("choice {");
ctx.with_indent(|ctx| {
for &case in cases {
ctx.write("case ");
render_block_expr(builder, ctx, case);
ctx.writeln(",");
}
});
ctx.write("}");
}
Op::SubBlock { sub_block } => {
render_block_expr(builder, ctx, *sub_block);
}
Op::Terminate {
unreachable_result_type,
} => {
ctx.write(format_args!(
"terminate<{}>",
type_ident(*unreachable_result_type),
));
}
Op::NewHeapCell => {
ctx.write("new_heap_cell");
}
Op::Touch => {
ctx.write("touch");
}
Op::RecursiveTouch => {
ctx.write("recursive_touch");
}
Op::UpdateWriteOnly { update_mode_var } => {
ctx.write(format_args!(
"update_write_only[{}]",
update_mode_string(&builder.update_mode_vars[update_mode_var]),
));
}
Op::EmptyBag { item_type } => {
ctx.write(format_args!("empty_bag<{}>", type_ident(*item_type)));
}
Op::BagInsert => {
ctx.write("bag_insert");
}
Op::BagGet => {
ctx.write("bag_get");
}
Op::BagRemove => {
ctx.write("bag_remove");
}
Op::MakeTuple => {
ctx.write("make_tuple");
}
Op::GetTupleField { field_idx } => {
ctx.write(format_args!("get_tuple_field {}", field_idx));
}
Op::MakeUnion {
variant_types,
variant_idx,
} => {
ctx.write("make_union<");
delimit(
ctx,
variant_types.iter(),
|ctx| {
ctx.write(", ");
},
|ctx, variant_type| {
ctx.write(type_ident(*variant_type));
},
);
ctx.write(format_args!("> {}", variant_idx));
}
Op::UnwrapUnion { variant_idx } => {
ctx.write(format_args!("unwrap_union {}", variant_idx));
}
Op::MakeNamed { named_mod, named } => {
ctx.write(format_args!(
"make_named {}::{}",
mod_name_string(named_mod),
type_name_string(named),
));
}
Op::UnwrapNamed { named_mod, named } => {
ctx.write(format_args!(
"unwrap_named {}::{}",
mod_name_string(named_mod),
type_name_string(named),
));
}
};
}
fn render_statement(builder: &ExprBuilder, ctx: &mut RenderContext, value_id: ValueId) {
match builder.vals.node(value_id) {
Node {
op: Op::DeclareContinuation { continuation },
inputs: [],
} => {
render_continuation_def(builder, ctx, *continuation);
}
Node { op, inputs } => {
ctx.write(format_args!("let {} = ", val_ident(value_id)));
render_op(builder, ctx, op);
ctx.write(" (");
delimit(
ctx,
inputs.iter(),
|ctx| {
ctx.write(", ");
},
|ctx, input| {
ctx.write(val_ident(*input));
},
);
ctx.writeln(");");
}
}
}
fn render_continuation_def(
builder: &ExprBuilder,
ctx: &mut RenderContext,
continuation: ContinuationId,
) {
let info = builder.continuations[continuation];
ctx.write(format_args!(
"continuation {} ({}: {}) -> {} ",
continuation_ident(continuation),
val_ident(info.arg),
type_ident(info.arg_type),
type_ident(info.ret_type),
));
render_block_expr(
builder,
ctx,
info.body
.expect("builder API should have verified that continuation body is defined"),
);
ctx.writeln("");
}
fn render_block_expr(builder: &ExprBuilder, ctx: &mut RenderContext, block_expr: BlockExpr) {
let BlockExpr(block, ret_val) = block_expr;
ctx.writeln("{");
ctx.with_indent(|ctx| {
for value in builder.blocks.block_values(block) {
render_statement(builder, ctx, value);
}
ctx.writeln(val_ident(ret_val))
});
ctx.write("}");
}
fn render_type_node(ctx: &mut RenderContext, node: Node<TypeId, TypeOp>) {
match node {
Node {
op: TypeOp::Named { named_mod, named },
inputs,
} => {
debug_assert!(inputs.is_empty());
ctx.write(format_args!(
"{}::{}",
mod_name_string(named_mod),
type_name_string(named)
));
}
Node {
op: TypeOp::Tuple,
inputs,
} => {
ctx.write("(");
delimit(
ctx,
inputs.iter(),
|ctx| {
ctx.write(", ");
},
|ctx, input| {
ctx.write(type_ident(*input));
},
);
ctx.write(")");
}
Node {
op: TypeOp::Union,
inputs,
} => {
ctx.write("union { ");
delimit(
ctx,
inputs.iter(),
|ctx| {
ctx.write(", ");
},
|ctx, input| {
ctx.write(type_ident(*input));
},
);
if !inputs.is_empty() {
ctx.write(" ");
}
ctx.write("}");
}
Node {
op: TypeOp::HeapCell,
inputs,
} => {
debug_assert!(inputs.is_empty());
ctx.write("heap_cell");
}
Node {
op: TypeOp::Bag,
inputs,
} => {
debug_assert_eq!(inputs.len(), 1);
ctx.write(format_args!("bag<{}>", type_ident(inputs[0])));
}
}
}
fn render_type_bindings(ctx: &mut RenderContext, types: &OpGraph<TypeId, TypeOp>) {
ctx.writeln("{");
ctx.with_indent(|ctx| {
for type_id in types.count().iter() {
ctx.write(format_args!("type {} = ", type_ident(type_id)));
render_type_node(ctx, types.node(type_id));
ctx.writeln(";");
}
});
ctx.write("}");
}
fn render_func_def(ctx: &mut RenderContext, name: &FuncNameBuf, func_def: &FuncDef) {
ctx.write(format_args!(
"fn {} ({}: {}) -> {} ",
func_name_string(name),
val_ident(func_def.builder.argument),
type_ident(func_def.arg_type),
type_ident(func_def.ret_type),
));
render_block_expr(&func_def.builder.expr_builder, ctx, func_def.root);
ctx.write(" where ");
render_type_bindings(ctx, &func_def.builder.expr_builder.type_builder.types);
ctx.writeln("");
}
fn render_const_def(ctx: &mut RenderContext, name: &ConstNameBuf, const_def: &ConstDef) {
ctx.write(format_args!(
"const {}: {} = ",
const_name_string(name),
type_ident(const_def.type_),
));
render_block_expr(&const_def.builder.expr_builder, ctx, const_def.root);
ctx.write(" where ");
render_type_bindings(ctx, &const_def.builder.expr_builder.type_builder.types);
ctx.writeln("");
}
fn render_type_def(ctx: &mut RenderContext, name: &TypeNameBuf, type_def: &TypeDef) {
ctx.write(format_args!(
"type {} = {} where ",
type_name_string(name),
type_ident(type_def.root),
));
render_type_bindings(ctx, &type_def.builder.types);
ctx.writeln("");
}
fn render_mod_def(ctx: &mut RenderContext, name: &ModNameBuf, mod_def: &ModDef) {
ctx.writeln(format_args!("mod {} {{", mod_name_string(name)));
ctx.with_indent(|ctx| {
let mut first_item = true;
let mut write_spacing = |ctx: &mut RenderContext| {
if !first_item {
ctx.writeln("");
}
first_item = false;
};
for (type_name, type_def) in &mod_def.type_defs {
write_spacing(ctx);
render_type_def(ctx, type_name, type_def);
}
for (const_name, const_def) in &mod_def.const_defs {
write_spacing(ctx);
render_const_def(ctx, const_name, const_def);
}
for (func_name, func_def) in &mod_def.func_defs {
write_spacing(ctx);
render_func_def(ctx, func_name, func_def);
}
});
ctx.writeln("}");
}
fn render_entry_point(
ctx: &mut RenderContext,
name: &EntryPointNameBuf,
entry_point: &(ModNameBuf, FuncNameBuf),
) {
let (mod_, func) = entry_point;
ctx.writeln(format_args!(
"entry_point {} = {}::{};",
entry_point_name_string(name),
mod_name_string(mod_),
func_name_string(func)
));
}
fn render_program(ctx: &mut RenderContext, program: &Program) {
ctx.writeln("program {");
ctx.with_indent(|ctx| {
let mut first_item = true;
let mut write_spacing = |ctx: &mut RenderContext| {
if !first_item {
ctx.writeln("");
}
first_item = false;
};
for (mod_name, mod_def) in &program.mods {
write_spacing(ctx);
render_mod_def(ctx, mod_name, mod_def);
}
for (entry_point_name, entry_point) in &program.entry_points {
write_spacing(ctx);
render_entry_point(ctx, entry_point_name, entry_point);
}
});
ctx.write("}");
}
pub fn render_program_to_string(program: &Program) -> String {
let mut ctx = RenderContext::new(2);
render_program(&mut ctx, program);
ctx.finish()
}