Merge remote-tracking branch 'origin/main' into expect-fx-codegen

This commit is contained in:
Folkert 2022-08-23 16:28:21 +02:00
commit a22e04361c
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
222 changed files with 10039 additions and 1945 deletions

View file

@ -3,7 +3,7 @@ name = "roc_cli"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "A CLI for Roc"
default-run = "roc"
@ -88,8 +88,8 @@ wasmer = { version = "2.2.1", optional = true, default-features = false, feature
wasmer-wasi = "2.2.1"
pretty_assertions = "1.0.0"
roc_test_utils = { path = "../test_utils" }
indoc = "1.0.3"
serial_test = "0.8.0"
indoc = "1.0.7"
serial_test = "0.9.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" }
strum = "0.24.0"

View file

@ -5,7 +5,10 @@ use roc_build::{
};
use roc_builtins::bitcode;
use roc_collections::VecMap;
use roc_load::{EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadingProblem, Threading};
use roc_load::{
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, Threading,
};
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
@ -35,6 +38,23 @@ pub struct BuiltFile {
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,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum BuildFileError<'a> {
LoadingProblem(LoadingProblem<'a>),
ErrorModule {
module: LoadedModule,
total_time: Duration,
},
}
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>(
arena: &'a Bump,
@ -46,29 +66,46 @@ pub fn build_file<'a>(
link_type: LinkType,
linking_strategy: LinkingStrategy,
precompiled: bool,
target_valgrind: bool,
threading: Threading,
wasm_dev_stack_bytes: Option<u32>,
) -> Result<BuiltFile, LoadingProblem<'a>> {
order: BuildOrdering,
) -> Result<BuiltFile, BuildFileError<'a>> {
let compilation_start = Instant::now();
let target_info = TargetInfo::from(target);
// Step 1: compile the app and generate the .o file
let subs_by_module = Default::default();
let exec_mode = match order {
BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
let load_config = LoadConfig {
target_info,
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
threading,
exec_mode: ExecutionMode::Executable,
exec_mode,
};
let loaded = roc_load::load_and_monomorphize(
let load_result = roc_load::load_and_monomorphize(
arena,
app_module_path.clone(),
subs_by_module,
load_config,
)?;
);
let loaded = match load_result {
Ok(loaded) => loaded,
Err(LoadMonomorphizedError::LoadingProblem(problem)) => {
return Err(BuildFileError::LoadingProblem(problem))
}
Err(LoadMonomorphizedError::ErrorModule(module)) => {
return Err(BuildFileError::ErrorModule {
module,
total_time: compilation_start.elapsed(),
})
}
};
use target_lexicon::Architecture;
let emit_wasm = matches!(target.architecture, Architecture::Wasm32);
@ -152,7 +189,6 @@ pub fn build_file<'a>(
target,
exposed_values,
exposed_closure_types,
target_valgrind,
);
// TODO try to move as much of this linking as possible to the precompiled
@ -161,9 +197,7 @@ pub fn build_file<'a>(
.prefix("roc_app")
.suffix(&format!(".{}", app_extension))
.tempfile()
.map_err(|err| {
todo!("TODO Gracefully handle tempfile creation error {:?}", err);
})?;
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
let app_o_file = app_o_file.path();
let buf = &mut String::with_capacity(1024);
@ -329,12 +363,12 @@ pub fn build_file<'a>(
link_type
)
.map_err(|_| {
todo!("gracefully handle `ld` failing to spawn.");
todo!("gracefully handle `ld` failing to spawn.")
})?;
let exit_status = child.wait().map_err(|_| {
todo!("gracefully handle error after `ld` spawned");
})?;
let exit_status = child
.wait()
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
if exit_status.success() {
problems
@ -377,7 +411,6 @@ fn spawn_rebuild_thread(
target: &Triple,
exported_symbols: Vec<String>,
exported_closure_types: Vec<String>,
target_valgrind: bool,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
@ -395,7 +428,6 @@ fn spawn_rebuild_thread(
&thread_local_target,
host_input_path.as_path(),
None,
target_valgrind,
);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
@ -408,7 +440,6 @@ fn spawn_rebuild_thread(
preprocessed_host_path.as_path(),
exported_symbols,
exported_closure_types,
target_valgrind,
);
}
LinkingStrategy::Legacy => {
@ -417,7 +448,6 @@ fn spawn_rebuild_thread(
&thread_local_target,
host_input_path.as_path(),
None,
target_valgrind,
);
}
}

View file

@ -31,10 +31,13 @@ pub mod build;
mod format;
pub use format::format;
use crate::build::{BuildFileError, BuildOrdering};
const DEFAULT_ROC_FILENAME: &str = "main.roc";
pub const CMD_BUILD: &str = "build";
pub const CMD_RUN: &str = "run";
pub const CMD_DEV: &str = "dev";
pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs";
@ -55,7 +58,6 @@ pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINKER: &str = "linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind";
pub const FLAG_CHECK: &str = "check";
pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb";
pub const ROC_FILE: &str = "ROC_FILE";
@ -94,11 +96,6 @@ pub fn build_app<'a>() -> Command<'a> {
.help("Store LLVM debug information in the generated program.")
.required(false);
let flag_valgrind = Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.help("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false);
let flag_time = Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
@ -150,7 +147,6 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(flag_wasm_stack_size_kb.clone())
.arg(
Arg::new(FLAG_TARGET)
@ -190,7 +186,6 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(
Arg::new(ROC_FILE)
.help("The .roc file for the main module")
@ -213,7 +208,19 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(roc_file_to_run.clone())
.arg(args_for_app.clone())
)
.subcommand(Command::new(CMD_DEV)
.about("`check` a .roc file, and then run it if there were no errors.")
.arg(flag_optimize.clone())
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(roc_file_to_run.clone())
.arg(args_for_app.clone())
)
@ -280,7 +287,6 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time)
.arg(flag_linker)
.arg(flag_precompiled)
.arg(flag_valgrind)
.arg(roc_file_to_run.required(false))
.arg(args_for_app);
@ -519,7 +525,10 @@ pub fn build(
.and_then(|s| s.parse::<u32>().ok())
.map(|x| x * 1024);
let target_valgrind = matches.is_present(FLAG_VALGRIND);
let build_ordering = match config {
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
_ => BuildOrdering::AlwaysBuild,
};
let res_binary_path = build_file(
&arena,
&triple,
@ -530,9 +539,9 @@ pub fn build(
link_type,
linking_strategy,
precompiled,
target_valgrind,
threading,
wasm_dev_stack_bytes,
build_ordering,
);
match res_binary_path {
@ -633,55 +642,13 @@ pub fn build(
x
}
BuildAndRunIfNoErrors => {
if problems.errors == 0 {
if problems.warnings > 0 {
println!(
"\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
problems.warnings,
if problems.warnings == 1 {
"warning"
} else {
"warnings"
},
total_time.as_millis(),
"".repeat(80)
);
}
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
let mut bytes = std::fs::read(&binary_path).unwrap();
let x = roc_run(
arena,
opt_level,
triple,
args,
&mut bytes,
expectations,
interns,
);
std::mem::forget(bytes);
x
} else {
let mut output = format!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with \x1B[32mroc run",
if problems.errors == 0 {
32 // green
} else {
33 // yellow
},
problems.errors,
if problems.errors == 1 {
"error"
} else {
"errors"
},
if problems.warnings == 0 {
32 // green
} else {
33 // yellow
},
debug_assert!(
problems.errors == 0,
"if there are errors, they should have been returned as an error variant"
);
if problems.warnings > 0 {
println!(
"\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
problems.warnings,
if problems.warnings == 1 {
"warning"
@ -689,22 +656,74 @@ pub fn build(
"warnings"
},
total_time.as_millis(),
"".repeat(80)
);
// If you're running "main.roc" then you can just do `roc run`
// to re-run the program.
if filename != DEFAULT_ROC_FILENAME {
output.push(' ');
output.push_str(&filename.to_string_lossy());
}
println!("{}\x1B[39m", output);
Ok(problems.exit_code())
}
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
let mut bytes = std::fs::read(&binary_path).unwrap();
let x = roc_run(
arena,
opt_level,
triple,
args,
&mut bytes,
expectations,
interns,
);
std::mem::forget(bytes);
x
}
}
}
Err(LoadingProblem::FormattedReport(report)) => {
Err(BuildFileError::ErrorModule {
mut module,
total_time,
}) => {
debug_assert!(module.total_problems() > 0);
let problems = roc_build::program::report_problems_typechecked(&mut module);
let mut output = format!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with \x1B[32mroc run",
if problems.errors == 0 {
32 // green
} else {
33 // yellow
},
problems.errors,
if problems.errors == 1 {
"error"
} else {
"errors"
},
if problems.warnings == 0 {
32 // green
} else {
33 // yellow
},
problems.warnings,
if problems.warnings == 1 {
"warning"
} else {
"warnings"
},
total_time.as_millis(),
);
// If you're running "main.roc" then you can just do `roc run`
// to re-run the program.
if filename != DEFAULT_ROC_FILENAME {
output.push(' ');
output.push_str(&filename.to_string_lossy());
}
println!("{}\x1B[39m", output);
Ok(problems.exit_code())
}
Err(BuildFileError::LoadingProblem(LoadingProblem::FormattedReport(report))) => {
print!("{}", report);
Ok(1)

View file

@ -1,9 +1,10 @@
use roc_build::link::LinkType;
use roc_cli::build::check_file;
use roc_cli::{
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DOCS,
CMD_EDIT, CMD_FORMAT, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION, DIRECTORY_OR_FILES,
FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, GLUE_FILE, ROC_FILE,
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV,
CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION,
DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, GLUE_FILE,
ROC_FILE,
};
use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
@ -64,6 +65,20 @@ fn main() -> io::Result<()> {
Ok(1)
}
}
Some((CMD_DEV, matches)) => {
if matches.is_present(ROC_FILE) {
build(
matches,
BuildConfig::BuildAndRunIfNoErrors,
Triple::host(),
LinkType::Executable,
)
} else {
eprintln!("What .roc file do you want to build? Specify it at the end of the `roc run` command.");
Ok(1)
}
}
Some((CMD_GLUE, matches)) => {
let input_path = Path::new(matches.value_of_os(ROC_FILE).unwrap());
let output_path = Path::new(matches.value_of_os(GLUE_FILE).unwrap());

View file

@ -25,7 +25,6 @@ mod cli_run {
use strum_macros::EnumIter;
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
const VALGRIND_FLAG: &str = concatcp!("--", roc_cli::FLAG_VALGRIND);
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER);
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK);
const PRECOMPILED_HOST: &str = concatcp!("--", roc_cli::FLAG_PRECOMPILED, "=true");
@ -137,14 +136,17 @@ mod cli_run {
expected_ending: &str,
use_valgrind: bool,
) {
// valgrind does not yet support avx512 instructions, see #1963.
// we can't enable this only when testing with valgrind because of host re-use between tests
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if is_x86_feature_detected!("avx512f") {
std::env::set_var("NO_AVX512", "1");
}
for cli_mode in CliMode::iter() {
let flags = {
let mut vec = flags.to_vec();
if use_valgrind {
vec.push(VALGRIND_FLAG);
}
vec.push("--max-threads=1");
vec.into_iter()
@ -295,13 +297,10 @@ mod cli_run {
let file_name = example_file(dir_name, example.filename);
match example.executable_filename {
"form" => {
// test is skipped until we upgrate to zig 0.9 / llvm 13
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
return;
}
"hello-gui" | "breakout" => {
// Since these require opening a window, we do `roc build` on them but don't run them.
"form" | "hello-gui" | "breakout" | "ruby" => {
// Since these require things the build system often doesn't have
// (e.g. GUIs open a window, Ruby needs ruby installed, WASM needs a browser)
// we do `roc build` on them but don't run them.
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], None);
return;
}
@ -443,6 +442,14 @@ mod cli_run {
expected_ending:"Roc <3 Zig!\n",
use_valgrind: true,
},
ruby:"ruby-interop" => Example {
filename: "main.roc",
executable_filename: "libhello",
stdin: &[],
input_file: None,
expected_ending:"",
use_valgrind: true,
},
fib:"algorithms" => Example {
filename: "fibonacci.roc",
executable_filename: "fibonacci",