Merge pull request #798 from rtfeldman/more-tea

More tea
This commit is contained in:
Richard Feldman 2020-12-13 22:55:05 -05:00 committed by GitHub
commit fbcde5dd9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 340 additions and 209 deletions

View file

@ -149,7 +149,8 @@ pub fn gen_from_mono_module(
// env.module.print_to_stderr(); // env.module.print_to_stderr();
// NOTE: If this fails, uncomment the above println to debug. // NOTE: If this fails, uncomment the above println to debug.
panic!( panic!(
"Non-main function failed LLVM verification. Uncomment the above println to debug!" r"Non-main function {:?} failed LLVM verification. Uncomment the above println to debug!",
fn_val.get_name(),
); );
} }
} }

View file

@ -18,7 +18,7 @@ pub enum ExposedModuleTypes {
} }
pub struct ConstrainedModule { pub struct ConstrainedModule {
pub unused_imports: MutSet<ModuleId>, pub unused_imports: MutMap<ModuleId, Region>,
pub constraint: Constraint, pub constraint: Constraint,
} }
@ -132,7 +132,7 @@ pub fn constrain_imports(
pub struct ConstrainableImports { pub struct ConstrainableImports {
pub imported_symbols: Vec<Import>, pub imported_symbols: Vec<Import>,
pub imported_aliases: MutMap<Symbol, Alias>, pub imported_aliases: MutMap<Symbol, Alias>,
pub unused_imports: MutSet<ModuleId>, pub unused_imports: MutMap<ModuleId, Region>,
} }
/// Run this before constraining imports. /// Run this before constraining imports.
@ -143,7 +143,7 @@ pub struct ConstrainableImports {
pub fn pre_constrain_imports( pub fn pre_constrain_imports(
home: ModuleId, home: ModuleId,
references: &MutSet<Symbol>, references: &MutSet<Symbol>,
imported_modules: MutSet<ModuleId>, imported_modules: MutMap<ModuleId, Region>,
exposed_types: &mut SubsByModule, exposed_types: &mut SubsByModule,
stdlib: &StdLib, stdlib: &StdLib,
) -> ConstrainableImports { ) -> ConstrainableImports {

View file

@ -2249,12 +2249,10 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena); let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena);
for layout in arguments { for layout in arguments {
argument_types.push(basic_type_from_layout( let arg_type = basic_type_from_layout(arena, context, layout, env.ptr_bytes);
arena, let arg_ptr_type = arg_type.ptr_type(AddressSpace::Generic);
context,
layout, argument_types.push(arg_ptr_type.into());
env.ptr_bytes,
));
} }
let function_pointer_type = { let function_pointer_type = {
@ -2310,6 +2308,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
let closure_data = builder.build_load(closure_data_ptr, "load_closure_data"); let closure_data = builder.build_load(closure_data_ptr, "load_closure_data");
let mut parameters = parameters; let mut parameters = parameters;
for param in parameters.iter_mut() {
debug_assert!(param.is_pointer_value());
*param = builder.build_load(param.into_pointer_value(), "load_param");
}
parameters.push(closure_data); parameters.push(closure_data);
let call_result = invoke_and_catch(env, function_value, function_ptr, &parameters, result_type); let call_result = invoke_and_catch(env, function_value, function_ptr, &parameters, result_type);
@ -2357,12 +2361,10 @@ pub fn build_function_caller<'a, 'ctx, 'env>(
let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena); let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena);
for layout in arguments { for layout in arguments {
argument_types.push(basic_type_from_layout( let arg_type = basic_type_from_layout(arena, context, layout, env.ptr_bytes);
arena, let arg_ptr_type = arg_type.ptr_type(AddressSpace::Generic);
context,
layout, argument_types.push(arg_ptr_type.into());
env.ptr_bytes,
));
} }
let function_pointer_type = { let function_pointer_type = {
@ -2429,6 +2431,13 @@ pub fn build_function_caller<'a, 'ctx, 'env>(
.build_bitcast(function_ptr, actual_function_type, "cast") .build_bitcast(function_ptr, actual_function_type, "cast")
.into_pointer_value(); .into_pointer_value();
let mut parameters = parameters;
for param in parameters.iter_mut() {
debug_assert!(param.is_pointer_value());
*param = builder.build_load(param.into_pointer_value(), "load_param");
}
let call_result = invoke_and_catch(env, function_value, function_ptr, &parameters, result_type); let call_result = invoke_and_catch(env, function_value, function_ptr, &parameters, result_type);
builder.build_store(output, call_result); builder.build_store(output, call_result);

View file

@ -106,12 +106,12 @@ impl Dependencies {
&mut self, &mut self,
module_id: ModuleId, module_id: ModuleId,
opt_effect_module: Option<ModuleId>, opt_effect_module: Option<ModuleId>,
dependencies: &MutSet<ModuleId>, dependencies: &MutMap<ModuleId, Region>,
goal_phase: Phase, goal_phase: Phase,
) -> MutSet<(ModuleId, Phase)> { ) -> MutSet<(ModuleId, Phase)> {
use Phase::*; use Phase::*;
for dep in dependencies.iter().copied() { for dep in dependencies.keys().copied() {
// to parse and generate constraints, the headers of all dependencies must be loaded! // to parse and generate constraints, the headers of all dependencies must be loaded!
// otherwise, we don't know whether an imported symbol is actually exposed // otherwise, we don't know whether an imported symbol is actually exposed
self.add_dependency_help(module_id, dep, Phase::Parse, Phase::LoadHeader); self.add_dependency_help(module_id, dep, Phase::Parse, Phase::LoadHeader);
@ -146,7 +146,7 @@ impl Dependencies {
let mut output = MutSet::default(); let mut output = MutSet::default();
// all the dependencies can be loaded // all the dependencies can be loaded
for dep in dependencies { for dep in dependencies.keys() {
// TODO figure out how to "load" (because it doesn't exist on the file system) the Effect module // TODO figure out how to "load" (because it doesn't exist on the file system) the Effect module
if Some(*dep) != opt_effect_module { if Some(*dep) != opt_effect_module {
@ -420,7 +420,7 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
let mut aliases = MutMap::default(); let mut aliases = MutMap::default();
for imported in parsed.imported_modules.iter() { for imported in parsed.imported_modules.keys() {
match state.module_cache.aliases.get(imported) { match state.module_cache.aliases.get(imported) {
None => unreachable!( None => unreachable!(
r"imported module {:?} did not register its aliases, so {:?} cannot use them", r"imported module {:?} did not register its aliases, so {:?} cannot use them",
@ -556,7 +556,7 @@ struct ModuleHeader<'a> {
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
deps_by_name: MutMap<PQModuleName<'a>, ModuleId>, deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
packages: MutMap<&'a str, PackageOrPath<'a>>, packages: MutMap<&'a str, PackageOrPath<'a>>,
imported_modules: MutSet<ModuleId>, imported_modules: MutMap<ModuleId, Region>,
exposes: Vec<Symbol>, exposes: Vec<Symbol>,
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
src: &'a [u8], src: &'a [u8],
@ -574,7 +574,7 @@ enum HeaderFor<'a> {
struct ConstrainedModule { struct ConstrainedModule {
module: Module, module: Module,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
imported_modules: MutSet<ModuleId>, imported_modules: MutMap<ModuleId, Region>,
constraint: Constraint, constraint: Constraint,
ident_ids: IdentIds, ident_ids: IdentIds,
var_store: VarStore, var_store: VarStore,
@ -631,7 +631,7 @@ struct ParsedModule<'a> {
src: &'a str, src: &'a str,
module_timing: ModuleTiming, module_timing: ModuleTiming,
deps_by_name: MutMap<PQModuleName<'a>, ModuleId>, deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
imported_modules: MutSet<ModuleId>, imported_modules: MutMap<ModuleId, Region>,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
parsed_defs: &'a [Located<roc_parse::ast::Def<'a>>], parsed_defs: &'a [Located<roc_parse::ast::Def<'a>>],
@ -659,6 +659,7 @@ enum Msg<'a> {
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
decls: Vec<Declaration>, decls: Vec<Declaration>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
unused_imports: MutMap<ModuleId, Region>,
}, },
FinishedAllTypeChecking { FinishedAllTypeChecking {
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
@ -870,6 +871,7 @@ enum BuildTask<'a> {
constraint: Constraint, constraint: Constraint,
var_store: VarStore, var_store: VarStore,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
unused_imports: MutMap<ModuleId, Region>,
}, },
BuildPendingSpecializations { BuildPendingSpecializations {
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -1607,6 +1609,7 @@ fn update<'a>(
solved_subs, solved_subs,
decls, decls,
mut module_timing, mut module_timing,
mut unused_imports,
} => { } => {
log!("solved types for {:?}", module_id); log!("solved types for {:?}", module_id);
module_timing.end_time = SystemTime::now(); module_timing.end_time = SystemTime::now();
@ -1616,6 +1619,15 @@ fn update<'a>(
.type_problems .type_problems
.insert(module_id, solved_module.problems); .insert(module_id, solved_module.problems);
let existing = match state.module_cache.can_problems.entry(module_id) {
Vacant(entry) => entry.insert(std::vec::Vec::new()),
Occupied(entry) => entry.into_mut(),
};
for (unused, region) in unused_imports.drain() {
existing.push(roc_problem::can::Problem::UnusedImport(unused, region));
}
let work = state.dependencies.notify(module_id, Phase::SolveTypes); let work = state.dependencies.notify(module_id, Phase::SolveTypes);
// if there is a platform, the Pkg-Config module provides host-exposed, // if there is a platform, the Pkg-Config module provides host-exposed,
@ -2350,7 +2362,7 @@ fn send_header<'a>(
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> = let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
Vec::with_capacity(imports.len()); Vec::with_capacity(imports.len());
let mut imported_modules: MutSet<ModuleId> = MutSet::default(); let mut imported_modules: MutMap<ModuleId, Region> = MutMap::default();
let mut scope_size = 0; let mut scope_size = 0;
for loc_entry in imports { for loc_entry in imports {
@ -2410,7 +2422,7 @@ fn send_header<'a>(
}; };
let module_id = module_ids.get_or_insert(&pq_module_name); let module_id = module_ids.get_or_insert(&pq_module_name);
imported_modules.insert(module_id); imported_modules.insert(module_id, region);
deps_by_name.insert(pq_module_name, module_id); deps_by_name.insert(pq_module_name, module_id);
@ -2534,7 +2546,7 @@ fn send_header_two<'a>(
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> = let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
Vec::with_capacity(imports.len()); Vec::with_capacity(imports.len());
let mut imported_modules: MutSet<ModuleId> = MutSet::default(); let mut imported_modules: MutMap<ModuleId, Region> = MutMap::default();
let num_exposes = provides.len(); let num_exposes = provides.len();
let mut deps_by_name: MutMap<PQModuleName, ModuleId> = let mut deps_by_name: MutMap<PQModuleName, ModuleId> =
@ -2542,7 +2554,7 @@ fn send_header_two<'a>(
// add standard imports // add standard imports
// TODO add Effect by default // TODO add Effect by default
imported_modules.insert(app_module_id); imported_modules.insert(app_module_id, Region::zero());
deps_by_name.insert( deps_by_name.insert(
PQModuleName::Unqualified(ModuleName::APP.into()), PQModuleName::Unqualified(ModuleName::APP.into()),
app_module_id, app_module_id,
@ -2594,7 +2606,7 @@ fn send_header_two<'a>(
}; };
let module_id = module_ids.get_or_insert(&pq_module_name); let module_id = module_ids.get_or_insert(&pq_module_name);
imported_modules.insert(module_id); imported_modules.insert(module_id, region);
deps_by_name.insert(pq_module_name, module_id); deps_by_name.insert(pq_module_name, module_id);
@ -2720,7 +2732,7 @@ impl<'a> BuildTask<'a> {
module_timing: ModuleTiming, module_timing: ModuleTiming,
constraint: Constraint, constraint: Constraint,
var_store: VarStore, var_store: VarStore,
imported_modules: MutSet<ModuleId>, imported_modules: MutMap<ModuleId, Region>,
exposed_types: &mut SubsByModule, exposed_types: &mut SubsByModule,
stdlib: &StdLib, stdlib: &StdLib,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
@ -2742,14 +2754,6 @@ impl<'a> BuildTask<'a> {
stdlib, stdlib,
); );
if !unused_imports.is_empty() {
todo!(
"TODO gracefully handle unused import {:?} from module {:?}",
&unused_imports,
home,
);
}
// Next, solve this module in the background. // Next, solve this module in the background.
Self::Solve { Self::Solve {
module, module,
@ -2759,6 +2763,7 @@ impl<'a> BuildTask<'a> {
var_store, var_store,
declarations, declarations,
module_timing, module_timing,
unused_imports,
} }
} }
} }
@ -2772,6 +2777,7 @@ fn run_solve<'a>(
constraint: Constraint, constraint: Constraint,
mut var_store: VarStore, mut var_store: VarStore,
decls: Vec<Declaration>, decls: Vec<Declaration>,
unused_imports: MutMap<ModuleId, Region>,
) -> Msg<'a> { ) -> Msg<'a> {
// We have more constraining work to do now, so we'll add it to our timings. // We have more constraining work to do now, so we'll add it to our timings.
let constrain_start = SystemTime::now(); let constrain_start = SystemTime::now();
@ -2819,6 +2825,7 @@ fn run_solve<'a>(
decls, decls,
solved_module, solved_module,
module_timing, module_timing,
unused_imports,
} }
} }
@ -3071,7 +3078,7 @@ fn fabricate_effects_module<'a>(
rigid_variables: module_output.rigid_variables, rigid_variables: module_output.rigid_variables,
}; };
let imported_modules = MutSet::default(); let imported_modules = MutMap::default();
// Should a effect module ever have a ModuleDocumentation? // Should a effect module ever have a ModuleDocumentation?
let module_docs = ModuleDocumentation { let module_docs = ModuleDocumentation {
@ -3615,6 +3622,7 @@ fn run_task<'a>(
var_store, var_store,
ident_ids, ident_ids,
declarations, declarations,
unused_imports,
} => Ok(run_solve( } => Ok(run_solve(
module, module,
ident_ids, ident_ids,
@ -3623,6 +3631,7 @@ fn run_task<'a>(
constraint, constraint,
var_store, var_store,
declarations, declarations,
unused_imports,
)), )),
BuildPendingSpecializations { BuildPendingSpecializations {
module_id, module_id,

View file

@ -64,7 +64,7 @@ impl TagName {
impl ModuleName { impl ModuleName {
// NOTE: After adding one of these, go to `impl ModuleId` and // NOTE: After adding one of these, go to `impl ModuleId` and
// add a corresponding ModuleId to there! // add a corresponding ModuleId to there!
pub const APP: &'static str = ""; // app modules have no module name pub const APP: &'static str = "#UserApp"; // app modules have this hardcoded name
pub const BOOL: &'static str = "Bool"; pub const BOOL: &'static str = "Bool";
pub const STR: &'static str = "Str"; pub const STR: &'static str = "Str";
pub const NUM: &'static str = "Num"; pub const NUM: &'static str = "Num";

View file

@ -571,6 +571,12 @@ impl<'a> Procs<'a> {
return; return;
} }
// If this is an imported symbol, let its home module make this specialization
if env.is_imported_symbol(name) {
add_needed_external(self, env, fn_var, name);
return;
}
// We're done with that tuple, so move layout back out to avoid cloning it. // We're done with that tuple, so move layout back out to avoid cloning it.
let (name, layout) = tuple; let (name, layout) = tuple;
@ -589,7 +595,7 @@ impl<'a> Procs<'a> {
// TODO should pending_procs hold a Rc<Proc>? // TODO should pending_procs hold a Rc<Proc>?
let partial_proc = match self.partial_procs.get(&symbol) { let partial_proc = match self.partial_procs.get(&symbol) {
Some(p) => p.clone(), Some(p) => p.clone(),
None => panic!("no partial_proc for {:?}", symbol), None => panic!("no partial_proc for {:?} in module {:?}", symbol, env.home),
}; };
// Mark this proc as in-progress, so if we're dealing with // Mark this proc as in-progress, so if we're dealing with
@ -695,6 +701,10 @@ impl<'a, 'i> Env<'a, 'i> {
Symbol::new(self.home, ident_id) Symbol::new(self.home, ident_id)
} }
pub fn is_imported_symbol(&self, symbol: Symbol) -> bool {
symbol.module_id() != self.home && !symbol.is_builtin()
}
} }
#[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)]
@ -904,13 +914,16 @@ where
D::Doc: Clone, D::Doc: Clone,
A: Clone, A: Clone,
{ {
use roc_module::ident::ModuleName;
if PRETTY_PRINT_IR_SYMBOLS { if PRETTY_PRINT_IR_SYMBOLS {
alloc.text(format!("{:?}", symbol)) alloc.text(format!("{:?}", symbol))
} else { } else {
let text = format!("{}", symbol); let text = format!("{}", symbol);
if text.starts_with('.') { if text.starts_with(ModuleName::APP) {
alloc.text("Test").append(text) let name: String = text.trim_start_matches(ModuleName::APP).into();
alloc.text("Test").append(name)
} else { } else {
alloc.text(text) alloc.text(text)
} }
@ -2129,6 +2142,96 @@ impl<'a> FunctionLayouts<'a> {
} }
} }
fn specialize_naked_symbol<'a>(
env: &mut Env<'a, '_>,
variable: Variable,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
symbol: Symbol,
) -> Stmt<'a> {
if procs.module_thunks.contains(&symbol) {
let partial_proc = procs.partial_procs.get(&symbol).unwrap();
let fn_var = partial_proc.annotation;
// This is a top-level declaration, which will code gen to a 0-arity thunk.
let result = call_by_name(
env,
procs,
fn_var,
symbol,
std::vec::Vec::new(),
layout_cache,
assigned,
env.arena.alloc(Stmt::Ret(assigned)),
);
return result;
} else if env.is_imported_symbol(symbol) {
match layout_cache.from_var(env.arena, variable, env.subs) {
Err(e) => panic!("invalid layout {:?}", e),
Ok(layout @ Layout::FunctionPointer(_, _)) => {
add_needed_external(procs, env, variable, symbol);
match hole {
Stmt::Jump(_, _) => todo!("not sure what to do in this case yet"),
_ => {
let expr = Expr::FunctionPointer(symbol, layout.clone());
let new_symbol = env.unique_symbol();
return Stmt::Let(
new_symbol,
expr,
layout,
env.arena.alloc(Stmt::Ret(new_symbol)),
);
}
}
}
Ok(_) => {
// this is a 0-arity thunk
let result = call_by_name(
env,
procs,
variable,
symbol,
std::vec::Vec::new(),
layout_cache,
assigned,
env.arena.alloc(Stmt::Ret(assigned)),
);
return result;
}
}
}
// A bit ugly, but it does the job
match hole {
Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([symbol])),
_ => {
let result = Stmt::Ret(symbol);
let original = symbol;
// we don't have a more accurate variable available, which means the variable
// from the partial_proc will be used. So far that has given the correct
// result, but I'm not sure this will continue to be the case in more complex
// examples.
let opt_fn_var = None;
// if this is a function symbol, ensure that it's properly specialized!
reuse_function_symbol(
env,
procs,
layout_cache,
opt_fn_var,
symbol,
result,
original,
)
}
}
}
pub fn with_hole<'a>( pub fn with_hole<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr, can_expr: roc_can::expr::Expr,
@ -2387,85 +2490,7 @@ pub fn with_hole<'a>(
) )
} }
Var(symbol) => { Var(symbol) => {
if procs.module_thunks.contains(&symbol) { specialize_naked_symbol(env, variable, procs, layout_cache, assigned, hole, symbol)
let partial_proc = procs.partial_procs.get(&symbol).unwrap();
let fn_var = partial_proc.annotation;
// This is a top-level declaration, which will code gen to a 0-arity thunk.
let result = call_by_name(
env,
procs,
fn_var,
symbol,
std::vec::Vec::new(),
layout_cache,
assigned,
env.arena.alloc(Stmt::Ret(assigned)),
);
return result;
} else if symbol.module_id() != env.home && symbol.module_id() != ModuleId::ATTR {
match layout_cache.from_var(env.arena, variable, env.subs) {
Err(e) => panic!("invalid layout {:?}", e),
Ok(layout @ Layout::FunctionPointer(_, _)) => {
add_needed_external(procs, env, variable, symbol);
match hole {
Stmt::Jump(_, _) => todo!("not sure what to do in this case yet"),
_ => {
let expr = Expr::FunctionPointer(symbol, layout.clone());
let new_symbol = env.unique_symbol();
return Stmt::Let(
new_symbol,
expr,
layout,
env.arena.alloc(Stmt::Ret(new_symbol)),
);
}
}
}
Ok(_) => {
// this is a 0-arity thunk
let result = call_by_name(
env,
procs,
variable,
symbol,
std::vec::Vec::new(),
layout_cache,
assigned,
env.arena.alloc(Stmt::Ret(assigned)),
);
return result;
}
}
}
// A bit ugly, but it does the job
match hole {
Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([symbol])),
_ => {
let result = Stmt::Ret(symbol);
let original = symbol;
// we don't have a more accurate variable available, which means the variable
// from the partial_proc will be used. So far that has given the correct
// result, but I'm not sure this will continue to be the case in more complex
// examples.
let opt_fn_var = None;
// if this is a function symbol, ensure that it's properly specialized!
reuse_function_symbol(
env,
procs,
layout_cache,
opt_fn_var,
symbol,
result,
original,
)
}
}
} }
Tag { Tag {
variant_var, variant_var,
@ -2636,17 +2661,28 @@ pub fn with_hole<'a>(
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena); let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
for (label, _, _) in sorted_fields.into_iter() { enum Field {
Function(Symbol, Variable),
ValueSymbol,
Field(roc_can::expr::Field),
}
for (label, variable, _) in sorted_fields.into_iter() {
// TODO how should function pointers be handled here? // TODO how should function pointers be handled here?
use ReuseSymbol::*;
match fields.remove(&label) { match fields.remove(&label) {
Some(field) => match can_reuse_symbol(procs, &field.loc_expr.value) { Some(field) => match can_reuse_symbol(env, procs, &field.loc_expr.value) {
Some(reusable) => { Imported(symbol) | LocalFunction(symbol) => {
field_symbols.push(reusable); field_symbols.push(symbol);
can_fields.push(None); can_fields.push(Field::Function(symbol, variable));
} }
None => { Value(reusable) => {
field_symbols.push(reusable);
can_fields.push(Field::ValueSymbol);
}
NotASymbol => {
field_symbols.push(env.unique_symbol()); field_symbols.push(env.unique_symbol());
can_fields.push(Some(field)); can_fields.push(Field::Field(field));
} }
}, },
None => { None => {
@ -2666,16 +2702,32 @@ pub fn with_hole<'a>(
for (opt_field, symbol) in can_fields.into_iter().rev().zip(field_symbols.iter().rev()) for (opt_field, symbol) in can_fields.into_iter().rev().zip(field_symbols.iter().rev())
{ {
if let Some(field) = opt_field { match opt_field {
stmt = with_hole( Field::ValueSymbol => {
env, // this symbol is already defined; nothing to do
field.loc_expr.value, }
field.var, Field::Function(symbol, variable) => {
procs, stmt = reuse_function_symbol(
layout_cache, env,
*symbol, procs,
env.arena.alloc(stmt), layout_cache,
); Some(variable),
symbol,
stmt,
symbol,
);
}
Field::Field(field) => {
stmt = with_hole(
env,
field.loc_expr.value,
field.var,
procs,
layout_cache,
*symbol,
env.arena.alloc(stmt),
);
}
} }
} }
@ -3342,8 +3394,15 @@ pub fn with_hole<'a>(
// if the function expression (loc_expr) is already a symbol, // if the function expression (loc_expr) is already a symbol,
// re-use that symbol, and don't define its value again // re-use that symbol, and don't define its value again
let mut result; let mut result;
match can_reuse_symbol(procs, &loc_expr.value) { use ReuseSymbol::*;
Some(function_symbol) => { match can_reuse_symbol(env, procs, &loc_expr.value) {
LocalFunction(_) => {
unreachable!("if this was known to be a function, we would not be here")
}
Imported(_) => {
unreachable!("an imported value is never an anonymous function")
}
Value(function_symbol) => {
if let Layout::Closure(_, closure_fields, _) = full_layout { if let Layout::Closure(_, closure_fields, _) = full_layout {
// we're invoking a closure // we're invoking a closure
let closure_record_symbol = env.unique_symbol(); let closure_record_symbol = env.unique_symbol();
@ -3436,7 +3495,7 @@ pub fn with_hole<'a>(
); );
} }
} }
None => { NotASymbol => {
let function_symbol = env.unique_symbol(); let function_symbol = env.unique_symbol();
result = Stmt::Let( result = Stmt::Let(
@ -4677,15 +4736,33 @@ fn store_record_destruct<'a>(
/// We want to re-use symbols that are not function symbols /// We want to re-use symbols that are not function symbols
/// for any other expression, we create a new symbol, and will /// for any other expression, we create a new symbol, and will
/// later make sure it gets assigned the correct value. /// later make sure it gets assigned the correct value.
fn can_reuse_symbol<'a>(procs: &Procs<'a>, expr: &roc_can::expr::Expr) -> Option<Symbol> {
enum ReuseSymbol {
Imported(Symbol),
LocalFunction(Symbol),
Value(Symbol),
NotASymbol,
}
fn can_reuse_symbol<'a>(
env: &mut Env<'a, '_>,
procs: &Procs<'a>,
expr: &roc_can::expr::Expr,
) -> ReuseSymbol {
use ReuseSymbol::*;
if let roc_can::expr::Expr::Var(symbol) = expr { if let roc_can::expr::Expr::Var(symbol) = expr {
if procs.partial_procs.contains_key(&symbol) { let symbol = *symbol;
None
if env.is_imported_symbol(symbol) {
Imported(symbol)
} else if procs.partial_procs.contains_key(&symbol) {
LocalFunction(symbol)
} else { } else {
Some(*symbol) Value(symbol)
} }
} else { } else {
None NotASymbol
} }
} }
@ -4694,9 +4771,9 @@ fn possible_reuse_symbol<'a>(
procs: &Procs<'a>, procs: &Procs<'a>,
expr: &roc_can::expr::Expr, expr: &roc_can::expr::Expr,
) -> Symbol { ) -> Symbol {
match can_reuse_symbol(procs, expr) { match can_reuse_symbol(env, procs, expr) {
Some(s) => s, ReuseSymbol::Value(s) => s,
None => env.unique_symbol(), _ => env.unique_symbol(),
} }
} }
@ -4752,8 +4829,7 @@ fn reuse_function_symbol<'a>(
) -> Stmt<'a> { ) -> Stmt<'a> {
match procs.partial_procs.get(&original) { match procs.partial_procs.get(&original) {
None => { None => {
let is_imported = let is_imported = env.is_imported_symbol(original);
!(env.home == original.module_id() || original.module_id() == ModuleId::ATTR);
match arg_var { match arg_var {
Some(arg_var) if is_imported => { Some(arg_var) if is_imported => {
@ -4761,8 +4837,6 @@ fn reuse_function_symbol<'a>(
.from_var(env.arena, arg_var, env.subs) .from_var(env.arena, arg_var, env.subs)
.expect("creating layout does not fail"); .expect("creating layout does not fail");
add_needed_external(procs, env, arg_var, original);
procs.insert_passed_by_name( procs.insert_passed_by_name(
env, env,
arg_var, arg_var,
@ -4774,7 +4848,7 @@ fn reuse_function_symbol<'a>(
// an imported symbol is always a function pointer: // an imported symbol is always a function pointer:
// either it's a function, or a top-level 0-argument thunk // either it's a function, or a top-level 0-argument thunk
let expr = Expr::FunctionPointer(original, layout.clone()); let expr = Expr::FunctionPointer(original, layout.clone());
return Stmt::Let(original, expr, layout, env.arena.alloc(result)); return Stmt::Let(symbol, expr, layout, env.arena.alloc(result));
} }
_ => { _ => {
// danger: a foreign symbol may not be specialized! // danger: a foreign symbol may not be specialized!
@ -4914,19 +4988,25 @@ fn assign_to_symbol<'a>(
symbol: Symbol, symbol: Symbol,
result: Stmt<'a>, result: Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
// if this argument is already a symbol, we don't need to re-define it use ReuseSymbol::*;
if let roc_can::expr::Expr::Var(original) = loc_arg.value { match can_reuse_symbol(env, procs, &loc_arg.value) {
reuse_function_symbol( Imported(original) | LocalFunction(original) => {
env, // for functions we must make sure they are specialized correctly
procs, reuse_function_symbol(
layout_cache, env,
Some(arg_var), procs,
symbol, layout_cache,
result, Some(arg_var),
original, symbol,
) result,
} else { original,
with_hole( )
}
Value(_) => {
// symbol is already defined; nothing else to do here
result
}
NotASymbol => with_hole(
env, env,
loc_arg.value, loc_arg.value,
arg_var, arg_var,
@ -4934,7 +5014,7 @@ fn assign_to_symbol<'a>(
layout_cache, layout_cache,
symbol, symbol,
env.arena.alloc(result), env.arena.alloc(result),
) ),
} }
} }

View file

@ -28,15 +28,22 @@ pub fn can_problem<'b>(
.append(alloc.reflow(line)), .append(alloc.reflow(line)),
]) ])
} }
Problem::UnusedImport(module_id, region) => alloc.concat(vec![ Problem::UnusedImport(module_id, region) => {
alloc.reflow("Nothing from "), alloc.stack(vec![
alloc.module(module_id), alloc.concat(vec![
alloc.reflow(" is used in this module."), alloc.reflow("Nothing from "),
alloc.region(region), alloc.module(module_id),
alloc.reflow("Since "), alloc.reflow(" is used in this module."),
alloc.module(module_id), ]),
alloc.reflow(" isn't used, you don't need to import it."), alloc.region(region),
]), alloc.concat(vec![
alloc.reflow("Since "),
alloc.module(module_id),
alloc.reflow(" isn't used, you don't need to import it."),
])
])
}
Problem::ExposedButNotDefined(symbol) => { Problem::ExposedButNotDefined(symbol) => {
alloc.stack(vec![ alloc.stack(vec![
alloc alloc

View file

@ -3,5 +3,29 @@ app "effect-example"
imports [base.Cmd] imports [base.Cmd]
provides [ main ] to base provides [ main ] to base
main : I64 Model : I64
main = 42
Msg : [ Line Str ]
main = { init, update }
init : {} -> { model : Model, cmd : Cmd.Cmd Msg }
init = \{} ->
cmd =
Cmd.after (Cmd.putLine "Type a thing, and I'll say it back") \{} ->
Cmd.getLine (\l -> Line l)
{ model: 42, cmd }
update : Msg, Model -> { model : Model, cmd : Cmd.Cmd Msg }
update = \msg, model ->
when msg is
Line line ->
cmd =
Cmd.after (Cmd.putLine "You said:") \{} ->
Cmd.after (Cmd.putLine line) \{} ->
Cmd.after (Cmd.putLine "Type another thing, and I'll say it back") \{} ->
Cmd.getLine (\l -> Line l)
{ model: model + 1, cmd }

View file

@ -1,5 +1,5 @@
platform folkertdev/foo platform folkertdev/foo
requires { main : Effect {} } requires { main : {} }
exposes [] exposes []
packages {} packages {}
imports [Cmd] imports [Cmd]
@ -11,31 +11,9 @@ platform folkertdev/foo
getLine : Effect Str getLine : Effect Str
} }
mainForHost : mainForHost :
{ {
init : ({} -> { model: I64, cmd : (Cmd.Cmd [ Line Str ]) as Fx }) as Init, init : ({} -> { model: I64, cmd : (Cmd.Cmd [ Line Str ]) as Fx }) as Init,
update : ([ Line Str ], I64 -> { model: I64, cmd : Cmd.Cmd [ Line Str ] } ) as Update update : ([ Line Str ], I64 -> { model: I64, cmd : Cmd.Cmd [ Line Str ] } ) as Update
} }
mainForHost = mainForHost = main
{
init : \{} ->
{
model: 42,
cmd:
Cmd.after (Cmd.putLine "Type a thing, and I'll say it back") \{} ->
Cmd.getLine (\l -> Line l)
},
update : \msg, model ->
when msg is
Line line ->
cmd =
Cmd.after (Cmd.putLine "You said:") \{} ->
Cmd.after (Cmd.putLine line) \{} ->
Cmd.after (Cmd.putLine "Type another thing, and I'll say it back") \{} ->
Cmd.getLine (\l -> Line l)
{ model: model + 1, cmd }
}

View file

@ -17,7 +17,12 @@ extern "C" {
fn roc_main_size() -> i64; fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_1_Init_caller"] #[link_name = "roc__mainForHost_1_Init_caller"]
fn call_Init(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> (); fn _call_Init(
flags: &(),
function_pointer: *const u8,
closure_data: *const u8,
output: *mut u8,
) -> ();
#[link_name = "roc__mainForHost_1_Init_size"] #[link_name = "roc__mainForHost_1_Init_size"]
fn size_Init() -> i64; fn size_Init() -> i64;
@ -27,8 +32,8 @@ extern "C" {
#[link_name = "roc__mainForHost_1_Update_caller"] #[link_name = "roc__mainForHost_1_Update_caller"]
fn call_Update( fn call_Update(
msg: Msg, msg: &Msg,
model: Model, model: &Model,
function_pointer: *const u8, function_pointer: *const u8,
closure_data: *const u8, closure_data: *const u8,
output: *mut u8, output: *mut u8,
@ -41,7 +46,12 @@ extern "C" {
fn size_Update_result() -> i64; fn size_Update_result() -> i64;
#[link_name = "roc__mainForHost_1_Fx_caller"] #[link_name = "roc__mainForHost_1_Fx_caller"]
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> (); fn _call_Fx(
unit: &(),
function_pointer: *const u8,
closure_data: *const u8,
output: *mut u8,
) -> ();
#[link_name = "roc__mainForHost_1_Fx_size"] #[link_name = "roc__mainForHost_1_Fx_size"]
fn size_Fx() -> i64; fn size_Fx() -> i64;
@ -53,6 +63,17 @@ extern "C" {
fn size_Model() -> i64; fn size_Model() -> i64;
} }
unsafe fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> () {
// Fx (or Cmd on the roc side) is a thunk, so we know its first argument is a (pointer to) unit
_call_Fx(&(), function_pointer, closure_data, output)
}
unsafe fn call_Init(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> () {
// for now, we hardcode flags to be `()` (or `{}` on the roc side)
let flags = ();
_call_Init(&flags, function_pointer, closure_data, output)
}
#[no_mangle] #[no_mangle]
pub fn roc_fx_putChar(foo: i64) -> () { pub fn roc_fx_putChar(foo: i64) -> () {
let character = foo as u8 as char; let character = foo as u8 as char;
@ -152,9 +173,11 @@ unsafe fn run_update(
let buffer: *mut std::ffi::c_void = buffer; let buffer: *mut std::ffi::c_void = buffer;
let buffer: *mut u8 = buffer as *mut u8; let buffer: *mut u8 = buffer as *mut u8;
println!("let's try update!");
call_Update( call_Update(
msg, &msg,
model, &model,
function_pointer, function_pointer,
closure_data_ptr, closure_data_ptr,
buffer as *mut u8, buffer as *mut u8,