Merge pull request #4768 from roc-lang/expects-store-specialized-variable

Support using dbg/expect in polymorphic functions
This commit is contained in:
Folkert de Vries 2022-12-16 00:10:40 +01:00 committed by GitHub
commit f550f049db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 390 additions and 124 deletions

View File

@ -568,7 +568,7 @@ mod cli_run {
r#"
This expectation failed:
14 expect x != x
18 expect x != x
^^^^^^
When it failed, these variables had these values:
@ -576,8 +576,11 @@ mod cli_run {
x : Num *
x = 42
[<ignored for tests> 15:9] 42
[<ignored for tests> 16:9] "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests> 19:9] 42
[<ignored for tests> 20:9] "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests> 13:9] "abc"
[<ignored for tests> 13:9] 10
[<ignored for tests> 13:9] A (B C)
Program finished!
"#
),

View File

@ -4,3 +4,4 @@ libapp.so
dynhost
preprocessedhost
metadata
expects

View File

@ -9,9 +9,17 @@ expect
a == b
polyDbg = \x ->
dbg x
x
main =
x = 42
expect x != x
dbg x
dbg "Fjoer en ferdjer frieten oan dyn geve lea"
"Program finished!\n"
r = {x : polyDbg "abc", y: polyDbg 10u8, z : polyDbg (A (B C))}
when r is
_ -> "Program finished!\n"

View File

@ -1287,6 +1287,14 @@ fn lowlevel_spec<'a>(
builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code])
}
Dbg => {
let arguments = [env.symbols[&arguments[0]]];
let result_type =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
_other => {
// println!("missing {:?}", _other);
// TODO overly pessimstic

View File

@ -705,7 +705,7 @@ pub fn constrain_expr(
expected,
);
constraints.exists_many([], [cond_con, continuation_con])
constraints.exists_many([*variable], [cond_con, continuation_con])
}
If {

View File

@ -2585,6 +2585,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
condition: cond_symbol,
region,
lookups,
variables,
remainder,
} => {
let bd = env.builder;
@ -2619,6 +2620,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
*cond_symbol,
*region,
lookups,
variables,
);
if let LlvmBackendMode::BinaryDev = env.mode {
@ -2653,6 +2655,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
condition: cond_symbol,
region,
lookups,
variables,
remainder,
} => {
let bd = env.builder;
@ -2687,6 +2690,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
*cond_symbol,
*region,
lookups,
variables,
);
bd.build_unconditional_branch(then_block);

View File

@ -10,6 +10,7 @@ use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::ir::LookupType;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use roc_region::all::Region;
@ -143,6 +144,21 @@ pub(crate) fn notify_parent_dbg(env: &Env, shared_memory: &SharedMemoryPointer)
);
}
// Shape of expect frame:
//
// ===
// Fixed-size header
// ===
// /-- ptr_lookup_1 (ptr_size)
// | var_lookup_1 (u32)
// | ..
// | ptr_lookup_n (ptr_size)
// | var_lookup_n (u32)
// \-> lookup_val_1 (varsize)
// ..
// lookup_val_n (varsize)
//
#[allow(clippy::too_many_arguments)]
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
@ -151,6 +167,7 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
condition: Symbol,
region: Region,
lookups: &[Symbol],
lookup_variables: &[LookupType],
) {
let original_ptr = shared_memory.0;
@ -160,9 +177,11 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
let after_header = offset;
let space_for_offsets = env
.ptr_int()
.const_int((lookups.len() * env.target_info.ptr_size()) as _, false);
let space_for_offsets = env.ptr_int().const_int(
(lookups.len() * env.target_info.ptr_size() + lookups.len() * std::mem::size_of::<u32>())
as _,
false,
);
let mut lookup_starts = bumpalo::collections::Vec::with_capacity_in(lookups.len(), env.arena);
@ -203,14 +222,43 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
{
let mut offset = after_header;
for lookup_start in lookup_starts {
build_copy(env, original_ptr, offset, lookup_start.into());
for (lookup_start, lookup_var) in lookup_starts.into_iter().zip(lookup_variables) {
// Store the pointer to the value
{
build_copy(env, original_ptr, offset, lookup_start.into());
let ptr_width = env
.ptr_int()
.const_int(env.target_info.ptr_size() as _, false);
let ptr_width = env
.ptr_int()
.const_int(env.target_info.ptr_size() as _, false);
offset = env.builder.build_int_add(offset, ptr_width, "offset")
offset = env.builder.build_int_add(offset, ptr_width, "offset");
}
// Store the specialized variable of the value
{
let ptr = unsafe {
env.builder
.build_in_bounds_gep(original_ptr, &[offset], "at_current_offset")
};
let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::Generic);
let ptr = env
.builder
.build_pointer_cast(ptr, u32_ptr, "cast_ptr_type");
let var_value = env
.context
.i32_type()
.const_int(lookup_var.index() as _, false);
env.builder.build_store(ptr, var_value);
let var_size = env
.ptr_int()
.const_int(std::mem::size_of::<u32>() as _, false);
offset = env.builder.build_int_add(offset, var_size, "offset");
}
}
}

View File

@ -11,7 +11,7 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_error_macros::internal_error;
use roc_module::{low_level::LowLevel, symbol::Symbol};
use roc_mono::{
ir::HigherOrderLowLevel,
ir::{HigherOrderLowLevel, LookupType},
layout::{Builtin, LambdaSet, Layout, LayoutIds},
};
use roc_target::PtrWidth;
@ -1120,14 +1120,19 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
}
},
Dbg => {
// now what
arguments!(condition);
assert_eq!(args.len(), 2);
let condition = load_symbol(scope, &args[0]);
let dbg_spec_var_symbol = args[1];
if env.mode.runs_expects() {
let region = unsafe { std::mem::transmute::<_, roc_region::all::Region>(args[0]) };
let shared_memory = crate::llvm::expect::SharedMemoryPointer::get(env);
// HACK(dbg-spec-var): the specialized type variable is passed along as a fake symbol
let specialized_var =
unsafe { LookupType::from_index(dbg_spec_var_symbol.ident_id().index() as _) };
crate::llvm::expect::clone_to_shared_memory(
env,
scope,
@ -1136,6 +1141,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
args[0],
region,
&[args[0]],
&[specialized_var],
);
crate::llvm::expect::notify_parent_dbg(env, &shared_memory);

View File

@ -138,7 +138,6 @@ struct ModuleCache<'a> {
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
late_specializations: MutMap<ModuleId, LateSpecializationsModule<'a>>,
external_specializations_requested: MutMap<ModuleId, Vec<ExternalSpecializations<'a>>>,
expectations: VecMap<ModuleId, Expectations>,
/// Various information
imports: MutMap<ModuleId, MutSet<ModuleId>>,
@ -215,7 +214,6 @@ impl Default for ModuleCache<'_> {
can_problems: Default::default(),
type_problems: Default::default(),
sources: Default::default(),
expectations: Default::default(),
}
}
}
@ -435,6 +433,7 @@ fn start_phase<'a>(
decls,
ident_ids,
abilities_store,
expectations,
} = typechecked;
let mut imported_module_thunks = bumpalo::collections::Vec::new_in(arena);
@ -451,8 +450,8 @@ fn start_phase<'a>(
let derived_module = SharedDerivedModule::clone(&state.derived_module);
let build_expects = matches!(state.exec_mode, ExecutionMode::Test)
&& state.module_cache.expectations.contains_key(&module_id);
let build_expects =
matches!(state.exec_mode, ExecutionMode::Test) && expectations.is_some();
BuildTask::BuildPendingSpecializations {
layout_cache,
@ -467,6 +466,7 @@ fn start_phase<'a>(
// TODO: awful, how can we get rid of the clone?
exposed_by_module: state.exposed_types.clone(),
derived_module,
expectations,
build_expects,
}
}
@ -488,67 +488,90 @@ fn start_phase<'a>(
specializations_we_must_make.extend(derived_synth_specializations)
}
let (mut ident_ids, mut subs, mut procs_base, layout_cache, mut module_timing) =
if state.make_specializations_pass.current_pass() == 1
&& module_id == ModuleId::DERIVED_GEN
{
// This is the first time the derived module is introduced into the load
// graph. It has no abilities of its own or anything, just generate fresh
// information for it.
(
IdentIds::default(),
Subs::default(),
ProcsBase::default(),
LayoutCache::new(state.layout_interner.fork(), state.target_info),
ModuleTiming::new(Instant::now()),
)
} else if state.make_specializations_pass.current_pass() == 1 {
let found_specializations = state
.module_cache
.found_specializations
.remove(&module_id)
.unwrap();
let (
mut ident_ids,
mut subs,
expectations,
mut procs_base,
layout_cache,
mut module_timing,
) = if state.make_specializations_pass.current_pass() == 1
&& module_id == ModuleId::DERIVED_GEN
{
// This is the first time the derived module is introduced into the load
// graph. It has no abilities of its own or anything, just generate fresh
// information for it.
(
IdentIds::default(),
Subs::default(),
None, // no expectations for derived module
ProcsBase::default(),
LayoutCache::new(state.layout_interner.fork(), state.target_info),
ModuleTiming::new(Instant::now()),
)
} else if state.make_specializations_pass.current_pass() == 1 {
let found_specializations = state
.module_cache
.found_specializations
.remove(&module_id)
.unwrap();
let FoundSpecializationsModule {
ident_ids,
subs,
procs_base,
layout_cache,
module_timing,
abilities_store,
} = found_specializations;
let FoundSpecializationsModule {
ident_ids,
subs,
procs_base,
layout_cache,
module_timing,
abilities_store,
expectations,
} = found_specializations;
let our_exposed_types = state
.exposed_types
.get(&module_id)
.unwrap_or_else(|| {
internal_error!("Exposed types for {:?} missing", module_id)
})
.clone();
let our_exposed_types = state
.exposed_types
.get(&module_id)
.unwrap_or_else(|| {
internal_error!("Exposed types for {:?} missing", module_id)
})
.clone();
// Add our abilities to the world.
state.world_abilities.insert(
module_id,
abilities_store,
our_exposed_types.exposed_types_storage_subs,
);
// Add our abilities to the world.
state.world_abilities.insert(
module_id,
abilities_store,
our_exposed_types.exposed_types_storage_subs,
);
(ident_ids, subs, procs_base, layout_cache, module_timing)
} else {
let LateSpecializationsModule {
ident_ids,
subs,
module_timing,
layout_cache,
procs_base,
} = state
.module_cache
.late_specializations
.remove(&module_id)
.unwrap();
(
ident_ids,
subs,
expectations,
procs_base,
layout_cache,
module_timing,
)
} else {
let LateSpecializationsModule {
ident_ids,
subs,
expectations,
module_timing,
layout_cache,
procs_base,
} = state
.module_cache
.late_specializations
.remove(&module_id)
.unwrap();
(ident_ids, subs, procs_base, layout_cache, module_timing)
};
(
ident_ids,
subs,
expectations,
procs_base,
layout_cache,
module_timing,
)
};
if module_id == ModuleId::DERIVED_GEN {
load_derived_partial_procs(
@ -579,6 +602,7 @@ fn start_phase<'a>(
// TODO: awful, how can we get rid of the clone?
exposed_by_module: state.exposed_types.clone(),
derived_module,
expectations,
}
}
}
@ -679,6 +703,7 @@ pub struct TypeCheckedModule<'a> {
pub decls: Declarations,
pub ident_ids: IdentIds,
pub abilities_store: AbilitiesStore,
pub expectations: Option<Expectations>,
}
#[derive(Debug)]
@ -689,6 +714,7 @@ struct FoundSpecializationsModule<'a> {
subs: Subs,
module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
expectations: Option<Expectations>,
}
#[derive(Debug)]
@ -698,6 +724,7 @@ struct LateSpecializationsModule<'a> {
module_timing: ModuleTiming,
layout_cache: LayoutCache<'a>,
procs_base: ProcsBase<'a>,
expectations: Option<Expectations>,
}
#[derive(Debug, Default)]
@ -831,6 +858,7 @@ enum Msg<'a> {
module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
toplevel_expects: ToplevelExpects,
expectations: Option<Expectations>,
},
MadeSpecializations {
module_id: ModuleId,
@ -842,6 +870,7 @@ enum Msg<'a> {
update_mode_ids: UpdateModeIds,
module_timing: ModuleTiming,
subs: Subs,
expectations: Option<Expectations>,
},
/// The task is to only typecheck AND monomorphize modules
@ -852,6 +881,7 @@ enum Msg<'a> {
/// DO NOT use the one on state; that is left in an empty state after specialization is complete!
layout_interner: STLayoutInterner<'a>,
exposed_to_host: ExposedToHost,
module_expectations: VecMap<ModuleId, Expectations>,
},
FailedToParse(FileError<'a, SyntaxError<'a>>),
@ -1147,6 +1177,7 @@ enum BuildTask<'a> {
exposed_by_module: ExposedByModule,
abilities_store: AbilitiesStore,
derived_module: SharedDerivedModule,
expectations: Option<Expectations>,
build_expects: bool,
},
MakeSpecializations {
@ -1160,6 +1191,7 @@ enum BuildTask<'a> {
exposed_by_module: ExposedByModule,
world_abilities: WorldAbilities,
derived_module: SharedDerivedModule,
expectations: Option<Expectations>,
},
}
@ -1717,6 +1749,7 @@ fn state_thread_step<'a>(
subs,
layout_interner,
exposed_to_host,
module_expectations,
} => {
// We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty());
@ -1727,6 +1760,7 @@ fn state_thread_step<'a>(
subs,
layout_interner,
exposed_to_host,
module_expectations,
)?;
Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
@ -2608,22 +2642,19 @@ fn update<'a>(
.expect("root or this module is not yet known - that's a bug!")
};
if should_include_expects {
let opt_expectations = if should_include_expects {
let (path, _) = state.module_cache.sources.get(&module_id).unwrap();
let expectations = Expectations {
Some(Expectations {
expectations: loc_expects,
dbgs: loc_dbgs,
subs: solved_subs.clone().into_inner(),
path: path.to_owned(),
ident_ids: ident_ids.clone(),
};
state
.module_cache
.expectations
.insert(module_id, expectations);
}
})
} else {
None
};
let work = state.dependencies.notify(module_id, Phase::SolveTypes);
@ -2736,6 +2767,7 @@ fn update<'a>(
decls,
ident_ids,
abilities_store,
expectations: opt_expectations,
};
state
@ -2775,6 +2807,7 @@ fn update<'a>(
module_timing,
abilities_store,
toplevel_expects,
expectations,
} => {
log!("found specializations for {:?}", module_id);
@ -2797,6 +2830,7 @@ fn update<'a>(
subs,
module_timing,
abilities_store,
expectations,
};
state
@ -2822,6 +2856,7 @@ fn update<'a>(
external_specializations_requested,
module_timing,
layout_cache,
expectations,
..
} => {
debug_assert!(
@ -2843,6 +2878,7 @@ fn update<'a>(
subs,
layout_cache,
procs_base,
expectations,
},
);
@ -2900,6 +2936,9 @@ fn update<'a>(
);
}
let mut module_expectations =
VecMap::with_capacity(state.module_cache.module_names.len());
// Flush late-specialization module information to the top-level of the state
// where it will be visible to others, since we don't need late specialization
// anymore.
@ -2911,6 +2950,7 @@ fn update<'a>(
module_timing,
layout_cache: _layout_cache,
procs_base: _,
expectations,
},
) in state.module_cache.late_specializations.drain()
{
@ -2919,6 +2959,9 @@ fn update<'a>(
state.root_subs = Some(subs);
}
state.timings.insert(module_id, module_timing);
if let Some(expectations) = expectations {
module_expectations.insert(module_id, expectations);
}
#[cfg(debug_assertions)]
{
@ -2981,6 +3024,7 @@ fn update<'a>(
subs,
layout_interner,
exposed_to_host: state.exposed_to_host.clone(),
module_expectations,
})
.map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -3114,6 +3158,7 @@ fn finish_specialization<'a>(
subs: Subs,
layout_interner: STLayoutInterner<'a>,
exposed_to_host: ExposedToHost,
module_expectations: VecMap<ModuleId, Expectations>,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
if false {
println!(
@ -3154,7 +3199,6 @@ fn finish_specialization<'a>(
} = state;
let ModuleCache {
expectations,
type_problems,
can_problems,
sources,
@ -3245,7 +3289,7 @@ fn finish_specialization<'a>(
can_problems,
type_problems,
output_path,
expectations,
expectations: module_expectations,
exposed_to_host,
module_id: state.root_id,
subs,
@ -5230,6 +5274,7 @@ fn make_specializations<'a>(
world_abilities: WorldAbilities,
exposed_by_module: &ExposedByModule,
derived_module: SharedDerivedModule,
mut expectations: Option<Expectations>,
) -> Msg<'a> {
let make_specializations_start = Instant::now();
let mut update_mode_ids = UpdateModeIds::new();
@ -5237,6 +5282,7 @@ fn make_specializations<'a>(
let mut mono_env = roc_mono::ir::Env {
arena,
subs: &mut subs,
expectation_subs: expectations.as_mut().map(|e| &mut e.subs),
home,
ident_ids: &mut ident_ids,
target_info,
@ -5288,6 +5334,7 @@ fn make_specializations<'a>(
procedures,
update_mode_ids,
subs,
expectations,
external_specializations_requested,
module_timing,
}
@ -5307,6 +5354,7 @@ fn build_pending_specializations<'a>(
exposed_by_module: &ExposedByModule,
abilities_store: AbilitiesStore,
derived_module: SharedDerivedModule,
mut expectations: Option<Expectations>,
build_expects: bool,
) -> Msg<'a> {
let find_specializations_start = Instant::now();
@ -5327,6 +5375,7 @@ fn build_pending_specializations<'a>(
let mut mono_env = roc_mono::ir::Env {
arena,
subs: &mut subs,
expectation_subs: expectations.as_mut().map(|e| &mut e.subs),
home,
ident_ids: &mut ident_ids,
target_info,
@ -5716,6 +5765,7 @@ fn build_pending_specializations<'a>(
module_timing,
abilities_store,
toplevel_expects,
expectations,
}
}
@ -5757,6 +5807,8 @@ fn load_derived_partial_procs<'a>(
let mut mono_env = roc_mono::ir::Env {
arena,
subs,
// There are no derived expectations.
expectation_subs: None,
home,
ident_ids,
target_info,
@ -5916,6 +5968,7 @@ fn run_task<'a>(
abilities_store,
exposed_by_module,
derived_module,
expectations,
build_expects,
} => Ok(build_pending_specializations(
arena,
@ -5931,6 +5984,7 @@ fn run_task<'a>(
&exposed_by_module,
abilities_store,
derived_module,
expectations,
build_expects,
)),
MakeSpecializations {
@ -5944,6 +5998,7 @@ fn run_task<'a>(
world_abilities,
exposed_by_module,
derived_module,
expectations,
} => Ok(make_specializations(
arena,
module_id,
@ -5957,6 +6012,7 @@ fn run_task<'a>(
world_abilities,
&exposed_by_module,
derived_module,
expectations,
)),
}?;

View File

@ -594,6 +594,13 @@ impl IdentId {
pub const fn index(self) -> usize {
self.0 as usize
}
/// # Safety
///
/// The index is not guaranteed to know to exist.
pub unsafe fn from_index(index: u32) -> Self {
Self(index)
}
}
/// Stores a mapping between Ident and IdentId.

View File

@ -948,7 +948,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListIsUnique => arena.alloc_slice_copy(&[borrowed]),
Dbg => arena.alloc_slice_copy(&[borrowed]),
Dbg => arena.alloc_slice_copy(&[borrowed, /* dbg-spec-var */ irrelevant]),
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")

View File

@ -309,12 +309,14 @@ impl<'a, 'r> Ctx<'a, 'r> {
condition,
region: _,
lookups,
variables: _,
remainder,
}
| &Stmt::ExpectFx {
condition,
region: _,
lookups,
variables: _,
remainder,
} => {
self.check_sym_layout(

View File

@ -555,7 +555,13 @@ impl<'a, 'i> Context<'a, 'i> {
match &call_type {
LowLevel { op, .. } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let b = match op {
roc_module::low_level::LowLevel::Dbg => {
// NB(dbg-spec-var) second var is the Variable
self.add_dec_after_lowlevel(&arguments[..1], ps, b, b_live_vars)
}
_ => self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars),
};
let v = Expr::Call(crate::ir::Call {
call_type,
@ -1199,6 +1205,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition,
region,
lookups,
variables,
} => {
let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder);
@ -1206,6 +1213,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
});
@ -1221,6 +1229,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition,
region,
lookups,
variables,
} => {
let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder);
@ -1228,6 +1237,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
});

View File

@ -27,14 +27,14 @@ use roc_late_solve::storage::{ExternalModuleStorage, ExternalModuleStorageSnapsh
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
use roc_problem::can::{RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo;
use roc_types::subs::{
instantiate_rigids, Content, ExhaustiveMark, FlatType, RedundantMark, StorageSubs, Subs,
Variable, VariableSubsSlice,
instantiate_rigids, storage_copy_var_to, Content, ExhaustiveMark, FlatType, RedundantMark,
StorageSubs, Subs, Variable, VariableSubsSlice,
};
use std::collections::HashMap;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder};
@ -1465,6 +1465,9 @@ impl<'a> Specializations<'a> {
pub struct Env<'a, 'i> {
pub arena: &'a Bump,
pub subs: &'i mut Subs,
/// [Subs] to write specialized variables of lookups in expects.
/// [None] if this module doesn't produce any expects.
pub expectation_subs: Option<&'i mut Subs>,
pub home: ModuleId,
pub ident_ids: &'i mut IdentIds,
pub target_info: TargetInfo,
@ -1601,6 +1604,9 @@ pub fn cond<'a>(
pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
/// The specialized type of a lookup. Represented as a type-variable.
pub type LookupType = Variable;
#[derive(Clone, Debug, PartialEq)]
pub enum Stmt<'a> {
Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>),
@ -1622,6 +1628,7 @@ pub enum Stmt<'a> {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
variables: &'a [LookupType],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
@ -1629,6 +1636,7 @@ pub enum Stmt<'a> {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
variables: &'a [LookupType],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
@ -6568,12 +6576,14 @@ pub fn from_can<'a>(
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut lookup_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut specialized_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
} in lookups_in_cond.iter().copied()
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
@ -6584,16 +6594,28 @@ pub fn from_can<'a>(
),
None => symbol,
};
let expectation_subs = env
.expectation_subs
.as_deref_mut()
.expect("if expects are compiled, their subs should be available");
let spec_var = expectation_subs.fresh_unnamed_flex_var();
if !env.subs.is_function(var) {
// Exclude functions from lookups
lookups.push(symbol);
lookup_variables.push(var);
specialized_variables.push(spec_var);
}
}
let specialized_variables = specialized_variables.into_bump_slice();
let mut stmt = Stmt::Expect {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
variables: specialized_variables,
remainder: env.arena.alloc(rest),
};
@ -6607,6 +6629,10 @@ pub fn from_can<'a>(
env.arena.alloc(stmt),
);
// Now that the condition has been specialized, export the specialized types of our
// lookups into the expectation subs.
store_specialized_expectation_lookups(env, lookup_variables, specialized_variables);
stmt
}
@ -6619,12 +6645,14 @@ pub fn from_can<'a>(
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut lookup_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut specialized_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
} in lookups_in_cond.iter().copied()
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
@ -6635,16 +6663,28 @@ pub fn from_can<'a>(
),
None => symbol,
};
let expectation_subs = env
.expectation_subs
.as_deref_mut()
.expect("if expects are compiled, their subs should be available");
let spec_var = expectation_subs.fresh_unnamed_flex_var();
if !env.subs.is_function(var) {
// Exclude functions from lookups
lookups.push(symbol);
lookup_variables.push(var);
specialized_variables.push(spec_var);
}
}
let specialized_variables = specialized_variables.into_bump_slice();
let mut stmt = Stmt::ExpectFx {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
variables: specialized_variables,
remainder: env.arena.alloc(rest),
};
@ -6658,6 +6698,8 @@ pub fn from_can<'a>(
env.arena.alloc(stmt),
);
store_specialized_expectation_lookups(env, lookup_variables, specialized_variables);
stmt
}
@ -6669,12 +6711,23 @@ pub fn from_can<'a>(
} => {
let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache);
let spec_var = env
.expectation_subs
.as_mut()
.unwrap()
.fresh_unnamed_flex_var();
// HACK(dbg-spec-var): pass the specialized type variable along injected into a fake symbol
let dbg_spec_var_symbol = Symbol::new(ModuleId::ATTR, unsafe {
IdentId::from_index(spec_var.index())
});
// TODO: need to store the specialized variable of this dbg in the expectation_subs
let call = crate::ir::Call {
call_type: CallType::LowLevel {
op: LowLevel::Dbg,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([dbg_symbol]),
arguments: env.arena.alloc([dbg_symbol, dbg_spec_var_symbol]),
};
let dbg_layout = layout_cache
@ -6702,6 +6755,10 @@ pub fn from_can<'a>(
);
}
// Now that the dbg value has been specialized, export its specialized type into the
// expectations subs.
store_specialized_expectation_lookups(env, [variable], &[spec_var]);
stmt
}
@ -6740,6 +6797,21 @@ pub fn from_can<'a>(
}
}
fn store_specialized_expectation_lookups(
env: &mut Env,
lookup_variables: impl IntoIterator<Item = Variable>,
specialized_variables: &[Variable],
) {
let subs = &env.subs;
let expectation_subs = env.expectation_subs.as_deref_mut().unwrap();
for (lookup_var, stored_var) in lookup_variables.into_iter().zip(specialized_variables) {
let stored_specialized_var =
storage_copy_var_to(&mut Default::default(), subs, expectation_subs, lookup_var);
let stored_specialized_desc = expectation_subs.get(stored_specialized_var);
expectation_subs.union(*stored_var, stored_specialized_var, stored_specialized_desc);
}
}
fn to_opt_branches<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
@ -7070,6 +7142,7 @@ fn substitute_in_stmt_help<'a>(
condition,
region,
lookups,
variables,
remainder,
} => {
let new_remainder =
@ -7084,6 +7157,7 @@ fn substitute_in_stmt_help<'a>(
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
variables,
remainder: new_remainder,
};
@ -7094,6 +7168,7 @@ fn substitute_in_stmt_help<'a>(
condition,
region,
lookups,
variables,
remainder,
} => {
let new_remainder =
@ -7108,6 +7183,7 @@ fn substitute_in_stmt_help<'a>(
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
variables,
remainder: new_remainder,
};

View File

@ -195,6 +195,7 @@ fn function_s<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let continuation: &Stmt = remainder;
@ -207,6 +208,7 @@ fn function_s<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: new_continuation,
};
@ -218,6 +220,7 @@ fn function_s<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let continuation: &Stmt = remainder;
@ -230,6 +233,7 @@ fn function_s<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: new_continuation,
};
@ -438,6 +442,7 @@ fn function_d_main<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let (b, found) = function_d_main(env, x, c, remainder);
@ -447,6 +452,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -458,6 +464,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -468,6 +475,7 @@ fn function_d_main<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let (b, found) = function_d_main(env, x, c, remainder);
@ -477,6 +485,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -488,6 +497,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -650,6 +660,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition,
region,
lookups,
variables,
remainder,
} => {
let b = function_r(env, remainder);
@ -658,6 +669,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -668,6 +680,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition,
region,
lookups,
variables,
remainder,
} => {
let b = function_r(env, remainder);
@ -676,6 +689,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};

View File

@ -253,6 +253,7 @@ fn insert_jumps<'a>(
condition,
region,
lookups,
variables,
remainder,
} => match insert_jumps(
arena,
@ -266,6 +267,7 @@ fn insert_jumps<'a>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: cont,
})),
None => None,
@ -275,6 +277,7 @@ fn insert_jumps<'a>(
condition,
region,
lookups,
variables,
remainder,
} => match insert_jumps(
arena,
@ -288,6 +291,7 @@ fn insert_jumps<'a>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: cont,
})),
None => None,

View File

@ -8529,4 +8529,23 @@ mod solve_expr {
print_can_decls: true
);
}
#[test]
fn constrain_dbg_flex_var() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
polyDbg = \x ->
#^^^^^^^{-1}
dbg x
x
main = polyDbg ""
"#
),
@"polyDbg : a -[[polyDbg(1)]]-> a"
);
}
}

View File

@ -32,9 +32,10 @@ pub fn get_values<'a>(
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
start: *const u8,
start_offset: usize,
variables: &[Variable],
) -> (usize, Vec<Expr<'a>>) {
let mut result = Vec::with_capacity(variables.len());
number_of_lookups: usize,
) -> (usize, Vec<Expr<'a>>, Vec<Variable>) {
let mut result = Vec::with_capacity(number_of_lookups);
let mut result_vars = Vec::with_capacity(number_of_lookups);
let memory = ExpectMemory { start };
@ -45,13 +46,20 @@ pub fn get_values<'a>(
let app = arena.alloc(app);
for (i, variable) in variables.iter().enumerate() {
let start = app.memory.deref_usize(start_offset + i * 8);
for i in 0..number_of_lookups {
let size_of_lookup_header = 8 /* pointer to value */ + 4 /* type variable */;
let start = app
.memory
.deref_usize(start_offset + i * size_of_lookup_header);
let variable = app.memory.deref_u32(
start_offset + i * size_of_lookup_header + 8, /* skip the pointer */
);
let variable = unsafe { Variable::from_index(variable) };
app.offset = start;
let expr = {
let variable = *variable;
// TODO: pass layout_cache to jit_to_ast directly
let mut layout_cache = LayoutCache::new(layout_interner.fork(), target_info);
let layout = layout_cache.from_var(arena, variable, subs).unwrap();
@ -76,9 +84,10 @@ pub fn get_values<'a>(
};
result.push(expr);
result_vars.push(variable);
}
(app.offset, result)
(app.offset, result, result_vars)
}
#[cfg(not(windows))]

View File

@ -25,7 +25,7 @@ use roc_mono::{ir::OptLevel, layout::Layout};
use roc_region::all::Region;
use roc_reporting::{error::expect::Renderer, report::RenderTarget};
use roc_target::TargetInfo;
use roc_types::subs::{Subs, Variable};
use roc_types::subs::Subs;
use target_lexicon::Triple;
pub struct ExpectMemory<'a> {
@ -471,7 +471,7 @@ pub fn render_dbgs_in_memory<'a>(
)
}
fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> (Vec<Symbol>, Vec<Variable>) {
fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> Vec<Symbol> {
lookups
.iter()
.filter_map(
@ -485,11 +485,11 @@ fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> (Vec<Symbol>,
if subs.is_function(*var) {
None
} else {
Some((*symbol, *var))
Some(*symbol)
}
},
)
.unzip()
.collect()
}
#[allow(clippy::too_many_arguments)]
@ -523,15 +523,7 @@ fn render_dbg_failure<'a>(
let subs = arena.alloc(&mut data.subs);
let current = ExpectLookup {
symbol: current.symbol,
var: current.var,
ability_info: current.ability_info,
};
let (_symbols, variables) = split_expect_lookups(subs, &[current]);
let (offset, expressions) = crate::get_values(
let (offset, expressions, _variables) = crate::get_values(
target_info,
arena,
subs,
@ -539,7 +531,7 @@ fn render_dbg_failure<'a>(
layout_interner,
start,
frame.start_offset,
&variables,
1,
);
renderer.render_dbg(writer, &expressions, expect_region, failure_region)?;
@ -574,24 +566,23 @@ fn render_expect_failure<'a>(
None => panic!("region {failure_region:?} not in list of expects"),
Some(current) => current,
};
let subs = arena.alloc(&mut data.subs);
let (symbols, variables) = split_expect_lookups(subs, current);
let symbols = split_expect_lookups(&data.subs, current);
let (offset, expressions) = crate::get_values(
let (offset, expressions, variables) = crate::get_values(
target_info,
arena,
subs,
&data.subs,
interns,
layout_interner,
start,
frame.start_offset,
&variables,
symbols.len(),
);
renderer.render_failure(
writer,
subs,
&mut data.subs,
&symbols,
&variables,
&expressions,