mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Dynamically load find/make specializations graph if there are no type errors
This commit is contained in:
parent
34c3f266e0
commit
b809d6d452
4 changed files with 153 additions and 12 deletions
|
@ -35,6 +35,13 @@ pub struct BuiltFile {
|
||||||
pub interns: Interns,
|
pub interns: Interns,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum BuildOrdering {
|
||||||
|
/// Run up through typechecking first; continue building iff that is successful.
|
||||||
|
BuildIfChecks,
|
||||||
|
/// Always build the Roc binary, even if there are type errors.
|
||||||
|
AlwaysBuild,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn build_file<'a>(
|
pub fn build_file<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
|
@ -48,6 +55,7 @@ pub fn build_file<'a>(
|
||||||
precompiled: bool,
|
precompiled: bool,
|
||||||
threading: Threading,
|
threading: Threading,
|
||||||
wasm_dev_stack_bytes: Option<u32>,
|
wasm_dev_stack_bytes: Option<u32>,
|
||||||
|
order: BuildOrdering,
|
||||||
) -> Result<BuiltFile, LoadingProblem<'a>> {
|
) -> Result<BuiltFile, LoadingProblem<'a>> {
|
||||||
let compilation_start = Instant::now();
|
let compilation_start = Instant::now();
|
||||||
let target_info = TargetInfo::from(target);
|
let target_info = TargetInfo::from(target);
|
||||||
|
@ -55,12 +63,17 @@ pub fn build_file<'a>(
|
||||||
// Step 1: compile the app and generate the .o file
|
// Step 1: compile the app and generate the .o file
|
||||||
let subs_by_module = Default::default();
|
let subs_by_module = Default::default();
|
||||||
|
|
||||||
|
let exec_mode = match order {
|
||||||
|
BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
|
||||||
|
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
|
||||||
|
};
|
||||||
|
|
||||||
let load_config = LoadConfig {
|
let load_config = LoadConfig {
|
||||||
target_info,
|
target_info,
|
||||||
// TODO: expose this from CLI?
|
// TODO: expose this from CLI?
|
||||||
render: RenderTarget::ColorTerminal,
|
render: RenderTarget::ColorTerminal,
|
||||||
threading,
|
threading,
|
||||||
exec_mode: ExecutionMode::Executable,
|
exec_mode,
|
||||||
};
|
};
|
||||||
let loaded = roc_load::load_and_monomorphize(
|
let loaded = roc_load::load_and_monomorphize(
|
||||||
arena,
|
arena,
|
||||||
|
|
|
@ -33,6 +33,8 @@ pub mod build;
|
||||||
mod format;
|
mod format;
|
||||||
pub use format::format;
|
pub use format::format;
|
||||||
|
|
||||||
|
use crate::build::BuildOrdering;
|
||||||
|
|
||||||
const DEFAULT_ROC_FILENAME: &str = "main.roc";
|
const DEFAULT_ROC_FILENAME: &str = "main.roc";
|
||||||
|
|
||||||
pub const CMD_BUILD: &str = "build";
|
pub const CMD_BUILD: &str = "build";
|
||||||
|
@ -521,6 +523,10 @@ pub fn build(
|
||||||
.and_then(|s| s.parse::<u32>().ok())
|
.and_then(|s| s.parse::<u32>().ok())
|
||||||
.map(|x| x * 1024);
|
.map(|x| x * 1024);
|
||||||
|
|
||||||
|
let build_ordering = match config {
|
||||||
|
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
|
||||||
|
_ => BuildOrdering::AlwaysBuild,
|
||||||
|
};
|
||||||
let res_binary_path = build_file(
|
let res_binary_path = build_file(
|
||||||
&arena,
|
&arena,
|
||||||
&triple,
|
&triple,
|
||||||
|
@ -533,6 +539,7 @@ pub fn build(
|
||||||
precompiled,
|
precompiled,
|
||||||
threading,
|
threading,
|
||||||
wasm_dev_stack_bytes,
|
wasm_dev_stack_bytes,
|
||||||
|
build_ordering,
|
||||||
);
|
);
|
||||||
|
|
||||||
match res_binary_path {
|
match res_binary_path {
|
||||||
|
|
|
@ -130,13 +130,15 @@ pub enum ExecutionMode {
|
||||||
Test,
|
Test,
|
||||||
Check,
|
Check,
|
||||||
Executable,
|
Executable,
|
||||||
|
/// Like [`ExecutionMode::Executable`], but stops in the presence of type errors.
|
||||||
|
ExecutableIfCheck,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionMode {
|
impl ExecutionMode {
|
||||||
fn goal_phase(&self) -> Phase {
|
fn goal_phase(&self) -> Phase {
|
||||||
match self {
|
match self {
|
||||||
ExecutionMode::Test | ExecutionMode::Executable => Phase::MakeSpecializations,
|
ExecutionMode::Test | ExecutionMode::Executable => Phase::MakeSpecializations,
|
||||||
ExecutionMode::Check => Phase::SolveTypes,
|
ExecutionMode::Check | ExecutionMode::ExecutableIfCheck => Phase::SolveTypes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,6 +170,22 @@ struct ModuleCache<'a> {
|
||||||
sources: MutMap<ModuleId, (PathBuf, &'a str)>,
|
sources: MutMap<ModuleId, (PathBuf, &'a str)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuleCache<'a> {
|
||||||
|
pub fn total_problems(&self) -> usize {
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
for problems in self.can_problems.values() {
|
||||||
|
total += problems.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
for problems in self.type_problems.values() {
|
||||||
|
total += problems.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ModuleCache<'_> {
|
impl Default for ModuleCache<'_> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let mut module_names = MutMap::default();
|
let mut module_names = MutMap::default();
|
||||||
|
@ -2379,7 +2397,12 @@ fn update<'a>(
|
||||||
.extend(solved_module.aliases.keys().copied());
|
.extend(solved_module.aliases.keys().copied());
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_host_exposed && state.goal_phase() == Phase::SolveTypes {
|
let finish_type_checking = is_host_exposed &&
|
||||||
|
(state.goal_phase() == Phase::SolveTypes)
|
||||||
|
// If we're running in check-and-then-build mode, only exit now there are errors.
|
||||||
|
&& (!matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) || state.module_cache.total_problems() > 0);
|
||||||
|
|
||||||
|
if finish_type_checking {
|
||||||
debug_assert!(work.is_empty());
|
debug_assert!(work.is_empty());
|
||||||
debug_assert!(state.dependencies.solved_all());
|
debug_assert!(state.dependencies.solved_all());
|
||||||
|
|
||||||
|
@ -2421,7 +2444,9 @@ fn update<'a>(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if state.goal_phase() > Phase::SolveTypes {
|
if state.goal_phase() > Phase::SolveTypes
|
||||||
|
|| matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
|
||||||
|
{
|
||||||
let layout_cache = state
|
let layout_cache = state
|
||||||
.layout_caches
|
.layout_caches
|
||||||
.pop()
|
.pop()
|
||||||
|
@ -2446,6 +2471,25 @@ fn update<'a>(
|
||||||
state.timings.insert(module_id, module_timing);
|
state.timings.insert(module_id, module_timing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let work = if is_host_exposed
|
||||||
|
&& matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
|
||||||
|
{
|
||||||
|
debug_assert!(
|
||||||
|
work.is_empty(),
|
||||||
|
"work left over after host exposed is checked"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the goal phase to target full codegen.
|
||||||
|
state.exec_mode = ExecutionMode::Executable;
|
||||||
|
|
||||||
|
// Load the find + make specializations portion of the dependency graph.
|
||||||
|
state
|
||||||
|
.dependencies
|
||||||
|
.load_find_and_make_specializations_after_check()
|
||||||
|
} else {
|
||||||
|
work
|
||||||
|
};
|
||||||
|
|
||||||
start_tasks(arena, &mut state, work, injector, worker_listeners)?;
|
start_tasks(arena, &mut state, work, injector, worker_listeners)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2803,7 +2847,7 @@ fn finish_specialization(
|
||||||
let entry_point = {
|
let entry_point = {
|
||||||
match exec_mode {
|
match exec_mode {
|
||||||
ExecutionMode::Test => EntryPoint::Test,
|
ExecutionMode::Test => EntryPoint::Test,
|
||||||
ExecutionMode::Executable => {
|
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
|
||||||
let path_to_platform = {
|
let path_to_platform = {
|
||||||
use PlatformPath::*;
|
use PlatformPath::*;
|
||||||
let package_name = match platform_path {
|
let package_name = match platform_path {
|
||||||
|
@ -5000,7 +5044,9 @@ fn build_pending_specializations<'a>(
|
||||||
// skip expectations if we're not going to run them
|
// skip expectations if we're not going to run them
|
||||||
match execution_mode {
|
match execution_mode {
|
||||||
ExecutionMode::Test => { /* fall through */ }
|
ExecutionMode::Test => { /* fall through */ }
|
||||||
ExecutionMode::Check | ExecutionMode::Executable => continue,
|
ExecutionMode::Check
|
||||||
|
| ExecutionMode::Executable
|
||||||
|
| ExecutionMode::ExecutableIfCheck => continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark this symbol as a top-level thunk before any other work on the procs
|
// mark this symbol as a top-level thunk before any other work on the procs
|
||||||
|
@ -5074,7 +5120,9 @@ fn build_pending_specializations<'a>(
|
||||||
// skip expectations if we're not going to run them
|
// skip expectations if we're not going to run them
|
||||||
match execution_mode {
|
match execution_mode {
|
||||||
ExecutionMode::Test => { /* fall through */ }
|
ExecutionMode::Test => { /* fall through */ }
|
||||||
ExecutionMode::Check | ExecutionMode::Executable => continue,
|
ExecutionMode::Check
|
||||||
|
| ExecutionMode::Executable
|
||||||
|
| ExecutionMode::ExecutableIfCheck => continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark this symbol as a top-level thunk before any other work on the procs
|
// mark this symbol as a top-level thunk before any other work on the procs
|
||||||
|
|
|
@ -166,11 +166,10 @@ impl<'a> Dependencies<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if goal_phase >= MakeSpecializations {
|
// Add "make specialization" dependents. Even if we're not targetting making
|
||||||
// Add make specialization dependents
|
// specializations right now, we may re-enter to do so later.
|
||||||
self.make_specializations_dependents
|
self.make_specializations_dependents
|
||||||
.add_succ(module_id, dependencies.iter().map(|dep| *dep.as_inner()));
|
.add_succ(module_id, dependencies.iter().map(|dep| *dep.as_inner()));
|
||||||
}
|
|
||||||
|
|
||||||
// add dependencies for self
|
// add dependencies for self
|
||||||
// phase i + 1 of a file always depends on phase i being completed
|
// phase i + 1 of a file always depends on phase i being completed
|
||||||
|
@ -374,6 +373,80 @@ impl<'a> Dependencies<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads the dependency graph to find and make specializations, and returns the next jobs to
|
||||||
|
/// be run.
|
||||||
|
///
|
||||||
|
/// This should be used when the compiler wants to build or run a Roc executable if and only if
|
||||||
|
/// previous stages succeed; in such cases we load the dependency graph dynamically.
|
||||||
|
pub fn load_find_and_make_specializations_after_check(&mut self) -> MutSet<(ModuleId, Phase)> {
|
||||||
|
let mut output = MutSet::default();
|
||||||
|
|
||||||
|
let mut make_specializations_dependents = MakeSpecializationsDependents::default();
|
||||||
|
let default_make_specializations_dependents_len = make_specializations_dependents.0.len();
|
||||||
|
std::mem::swap(
|
||||||
|
&mut self.make_specializations_dependents,
|
||||||
|
&mut make_specializations_dependents,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (&module, info) in make_specializations_dependents.0.iter_mut() {
|
||||||
|
debug_assert!(self.status.get_mut(&Job::Step(module, Phase::FindSpecializations)).is_none(), "should only have targetted solving types, but there is already a goal to find specializations");
|
||||||
|
debug_assert!(self.status.get_mut(&Job::Step(module, Phase::MakeSpecializations)).is_none(), "should only have targetted solving types, but there is already a goal to make specializations");
|
||||||
|
debug_assert!(
|
||||||
|
module == ModuleId::DERIVED_GEN || info.succ.contains(&ModuleId::DERIVED_GEN),
|
||||||
|
"derived module not accounted for in {:?}",
|
||||||
|
(module, info)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut has_find_specialization_dep = false;
|
||||||
|
for &module_dep in info.succ.iter() {
|
||||||
|
// The modules in `succ` are the modules for which specializations should be made
|
||||||
|
// after the current one. But, their specializations should be found before the
|
||||||
|
// current one.
|
||||||
|
if module_dep != ModuleId::DERIVED_GEN {
|
||||||
|
// We never find specializations for DERIVED_GEN
|
||||||
|
self.add_dependency(module, module_dep, Phase::FindSpecializations);
|
||||||
|
has_find_specialization_dep = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.add_dependency(module_dep, module, Phase::MakeSpecializations);
|
||||||
|
self.add_dependency(ModuleId::DERIVED_GEN, module, Phase::MakeSpecializations);
|
||||||
|
|
||||||
|
// `module_dep` can't make its specializations until the current module does.
|
||||||
|
info.has_pred = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if module != ModuleId::DERIVED_GEN {
|
||||||
|
self.add_to_status_for_phase(module, Phase::FindSpecializations);
|
||||||
|
self.add_dependency_help(
|
||||||
|
module,
|
||||||
|
module,
|
||||||
|
Phase::MakeSpecializations,
|
||||||
|
Phase::FindSpecializations,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.add_to_status_for_phase(module, Phase::MakeSpecializations);
|
||||||
|
|
||||||
|
if !has_find_specialization_dep && module != ModuleId::DERIVED_GEN {
|
||||||
|
// We don't depend on any other modules having their specializations found first,
|
||||||
|
// so start finding specializations from this module.
|
||||||
|
output.insert((module, Phase::FindSpecializations));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mem::swap(
|
||||||
|
&mut self.make_specializations_dependents,
|
||||||
|
&mut make_specializations_dependents,
|
||||||
|
);
|
||||||
|
debug_assert_eq!(
|
||||||
|
make_specializations_dependents.0.len(),
|
||||||
|
default_make_specializations_dependents_len,
|
||||||
|
"more modules were added to the graph: {:?}",
|
||||||
|
make_specializations_dependents
|
||||||
|
);
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
/// Load the entire "make specializations" dependency graph and start from the top.
|
/// Load the entire "make specializations" dependency graph and start from the top.
|
||||||
pub fn reload_make_specialization_pass(&mut self) -> MutSet<(ModuleId, Phase)> {
|
pub fn reload_make_specialization_pass(&mut self) -> MutSet<(ModuleId, Phase)> {
|
||||||
let mut output = MutSet::default();
|
let mut output = MutSet::default();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue