Split out an optional "llvm" feature

Also move OptLevel out of roc_gen (which
should really be called gen_llvm) and into roc_mono,
so it's no longer coupled to LLVM.
This commit is contained in:
Richard Feldman 2021-06-06 00:17:42 -04:00
parent 0e5619c422
commit b05342c678
14 changed files with 143 additions and 76 deletions

View file

@ -32,6 +32,12 @@ members = [
"roc_std",
"docs"
]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more.
#
# Without the `-p` flag, cargo ignores `--no-default-features` when you have a
# workspace, and without `resolver = "2"` here, you can't use `-p` like this.
resolver = "2"
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]

View file

@ -15,7 +15,11 @@ test = false
bench = false
[features]
default = ["target-x86"]
default = ["target-x86", "llvm"]
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen", "roc_build/llvm"]
target-x86 = []
@ -45,8 +49,8 @@ roc_unify = { path = "../compiler/unify" }
roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_gen = { path = "../compiler/gen" }
roc_build = { path = "../compiler/build" }
roc_gen = { path = "../compiler/gen", optional = true }
roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor" }
@ -62,7 +66,7 @@ inlinable_string = "0.1"
libc = "0.2"
libloading = "0.6"
inkwell = { path = "../vendor/inkwell" }
inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.10"
tempfile = "3.1.0"
@ -74,7 +78,7 @@ quickcheck = "0.8"
quickcheck_macros = "0.8"
serial_test = "0.5"
tempfile = "3.1.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "cli_utils" }
# Keep the commented deps, they are commented because they require nightly rust
# criterion-perf-events = "0.1.3"

View file

@ -5,8 +5,8 @@ use roc_build::{
};
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
@ -32,6 +32,7 @@ pub struct BuiltFile {
pub total_time: Duration,
}
#[cfg(feature = "llvm")]
pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,

View file

@ -1,12 +1,12 @@
#[macro_use]
extern crate clap;
use build::{build_file, BuildOutcome, BuiltFile};
use build::{BuildOutcome, BuiltFile};
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
@ -116,7 +116,9 @@ pub enum BuildConfig {
BuildAndRun { roc_file_arg_index: usize },
}
#[cfg(feature = "llvm")]
pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use build::build_file;
use BuildConfig::*;
let arena = Bump::new();

View file

@ -1,11 +1,19 @@
use roc_cli::{
build, build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN,
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN,
DIRECTORY_OR_FILES, ROC_FILE,
};
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
#[cfg(feature = "llvm")]
use roc_cli::build;
#[cfg(not(feature = "llvm"))]
fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
panic!("Building without LLVM is not currently supported.");
}
fn main() -> io::Result<()> {
let matches = build_app().get_matches();

View file

@ -1,15 +1,12 @@
use const_format::concatcp;
#[cfg(feature = "llvm")]
use gen::{gen_and_eval, ReplOutput};
use roc_gen::llvm::build::OptLevel;
use roc_parse::parser::{EExpr, SyntaxError};
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline::Editor;
use rustyline_derive::{Completer, Helper, Hinter};
use std::borrow::Cow;
use std::io;
use target_lexicon::Triple;
const BLUE: &str = "\u{001b}[36m";
const PINK: &str = "\u{001b}[35m";
@ -30,7 +27,9 @@ pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n";
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
pub const CONT_PROMPT: &str = concatcp!(BLUE, "", END_COL, " ");
#[cfg(feature = "llvm")]
mod eval;
#[cfg(feature = "llvm")]
mod gen;
#[derive(Completer, Helper, Hinter)]
@ -107,7 +106,16 @@ impl Validator for InputValidator {
}
}
#[cfg(not(feature = "llvm"))]
pub fn main() -> io::Result<()> {
panic!("The REPL currently requires being built with LLVM.");
}
#[cfg(feature = "llvm")]
pub fn main() -> io::Result<()> {
use rustyline::error::ReadlineError;
use rustyline::Editor;
// To debug rustyline:
// <UNCOMMENT> env_logger::init();
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
@ -226,7 +234,11 @@ fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
#[cfg(feature = "llvm")]
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
use roc_mono::ir::OptLevel;
use target_lexicon::Triple;
gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)

View file

@ -8,9 +8,9 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens};
use roc_gen::llvm::build::OptLevel;
use roc_gen::llvm::externs::add_default_roc_externs;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use roc_parse::parser::SyntaxError;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf};

View file

@ -19,7 +19,7 @@ roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_gen = { path = "../gen" }
roc_gen = { path = "../gen", optional = true }
roc_reporting = { path = "../reporting" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
@ -28,7 +28,7 @@ inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
inkwell = { path = "../../vendor/inkwell" }
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.10"
[dev-dependencies]
@ -39,6 +39,10 @@ quickcheck = "0.8"
quickcheck_macros = "0.8"
[features]
default = ["llvm"]
target-arm = []
target-aarch64 = []
target-webassembly = []
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen"]

View file

@ -1,16 +1,14 @@
use crate::target;
use crate::target::arch_str;
use inkwell::module::Module;
use inkwell::targets::{CodeModel, FileType, RelocMode};
#[cfg(feature = "llvm")]
use libloading::{Error, Library};
use roc_gen::llvm::build::OptLevel;
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use std::collections::HashMap;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple};
use tempfile::tempdir;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
@ -360,6 +358,9 @@ fn link_linux(
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
init_arch(target);
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Ok((
@ -477,12 +478,16 @@ fn link_macos(
))
}
#[cfg(feature = "llvm")]
pub fn module_to_dylib(
module: &Module,
module: &inkwell::module::Module,
target: &Triple,
opt_level: OptLevel,
) -> Result<Library, Error> {
let dir = tempdir().unwrap();
use crate::target::{self, convert_opt_level};
use inkwell::targets::{CodeModel, FileType, RelocMode};
let dir = tempfile::tempdir().unwrap();
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let mut app_o_file = file_path;
@ -492,7 +497,8 @@ pub fn module_to_dylib(
// Emit the .o file using position-indepedent code (PIC) - needed for dylibs
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine = target::target_machine(target, opt_level.into(), reloc, model).unwrap();
let target_machine =
target::target_machine(target, convert_opt_level(opt_level), reloc, model).unwrap();
target_machine
.write_to_file(module, FileType::Object, &app_o_file)
@ -529,3 +535,13 @@ fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
}
}
}
#[cfg(feature = "llvm")]
fn init_arch(target: &Triple) {
crate::target::init_arch(target);
}
#[cfg(not(feature = "llvm"))]
fn init_arch(_target: &Triple) {
panic!("Tried to initialize LLVM when crate was not built with `feature = \"llvm\"` enabled");
}

View file

@ -1,13 +1,14 @@
use crate::target;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::targets::{CodeModel, FileType, RelocMode};
#[cfg(feature = "llvm")]
use roc_gen::llvm::build::module_from_builtins;
#[cfg(feature = "llvm")]
pub use roc_gen::llvm::build::FunctionIterator;
use roc_gen::llvm::build::{module_from_builtins, OptLevel};
#[cfg(feature = "llvm")]
use roc_load::file::MonomorphizedModule;
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
#[cfg(feature = "llvm")]
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
use std::time::Duration;
#[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming {
@ -18,16 +19,24 @@ pub struct CodeGenTiming {
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[cfg(feature = "llvm")]
#[allow(clippy::cognitive_complexity)]
pub fn gen_from_mono_module(
arena: &Bump,
arena: &bumpalo::Bump,
mut loaded: MonomorphizedModule,
roc_file_path: &Path,
target: Triple,
target: target_lexicon::Triple,
app_o_file: &Path,
opt_level: OptLevel,
emit_debug_info: bool,
) -> CodeGenTiming {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::context::Context;
use inkwell::module::Linkage;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use std::time::SystemTime;
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
@ -87,9 +96,6 @@ pub fn gen_from_mono_module(
// module.strip_debug_info();
// mark our zig-defined builtins as internal
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::module::Linkage;
let app_ll_file = {
let mut temp = PathBuf::from(roc_file_path);
temp.set_extension("ll");
@ -226,7 +232,7 @@ pub fn gen_from_mono_module(
let reloc = RelocMode::Default;
let model = CodeModel::Default;
let target_machine =
target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
target::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap();
target_machine
.write_to_file(&env.module, FileType::Object, &app_o_file)

View file

@ -1,7 +1,10 @@
use inkwell::targets::{
CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple,
#[cfg(feature = "llvm")]
use inkwell::{
targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple},
OptimizationLevel,
};
use inkwell::OptimizationLevel;
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use target_lexicon::{Architecture, OperatingSystem, Triple};
pub fn target_triple_str(target: &Triple) -> &'static str {
@ -28,36 +31,20 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
}
}
/// NOTE: arch_str is *not* the same as the beginning of the magic target triple
/// string! For example, if it's "x86-64" here, the magic target triple string
/// will begin with "x86_64" (with an underscore) instead.
pub fn arch_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
#[cfg(feature = "llvm")]
pub fn init_arch(target: &Triple) {
match target.architecture {
Architecture::X86_64 => {
Target::initialize_x86(&InitializationConfig::default());
"x86-64"
}
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => {
Target::initialize_aarch64(&InitializationConfig::default());
"aarch64"
}
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
// NOTE: why not enable arm and wasm by default?
//
// We had some trouble getting them to link properly. This may be resolved in the
// future, or maybe it was just some weird configuration on one machine.
Target::initialize_arm(&InitializationConfig::default());
"arm"
}
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
Target::initialize_webassembly(&InitializationConfig::default());
"wasm32"
}
_ => panic!(
"TODO gracefully handle unsupported target architecture: {:?}",
@ -66,6 +53,26 @@ pub fn arch_str(target: &Triple) -> &'static str {
}
}
/// NOTE: arch_str is *not* the same as the beginning of the magic target triple
/// string! For example, if it's "x86-64" here, the magic target triple string
/// will begin with "x86_64" (with an underscore) instead.
pub fn arch_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
match target.architecture {
Architecture::X86_64 => "x86-64",
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => "aarch64",
Architecture::Arm(_) if cfg!(feature = "target-arm") => "arm",
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => "wasm32",
_ => panic!(
"TODO gracefully handle unsupported target architecture: {:?}",
target.architecture
),
}
}
#[cfg(feature = "llvm")]
pub fn target_machine(
target: &Triple,
opt: OptimizationLevel,
@ -83,3 +90,11 @@ pub fn target_machine(
model,
)
}
#[cfg(feature = "llvm")]
pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel {
match level {
OptLevel::Normal => OptimizationLevel::None,
OptLevel::Optimize => OptimizationLevel::Aggressive,
}
}

View file

@ -49,7 +49,8 @@ use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{
BranchInfo, CallType, ExceptionId, JoinPointId, ModifyRc, TopLevelFunctionLayout, Wrapped,
BranchInfo, CallType, ExceptionId, JoinPointId, ModifyRc, OptLevel, TopLevelFunctionLayout,
Wrapped,
};
use roc_mono::layout::{Builtin, InPlace, LambdaSet, Layout, LayoutIds, UnionLayout};
use target_lexicon::CallingConvention;
@ -86,21 +87,6 @@ macro_rules! debug_info_init {
}};
}
#[derive(Debug, Clone, Copy)]
pub enum OptLevel {
Normal,
Optimize,
}
impl From<OptLevel> for OptimizationLevel {
fn from(level: OptLevel) -> Self {
match level {
OptLevel::Normal => OptimizationLevel::None,
OptLevel::Optimize => OptimizationLevel::Aggressive,
}
}
}
/// Iterate over all functions in an llvm module
pub struct FunctionIterator<'ctx> {
next: Option<FunctionValue<'ctx>>,

View file

@ -43,6 +43,12 @@ macro_rules! return_on_layout_error {
};
}
#[derive(Debug, Clone, Copy)]
pub enum OptLevel {
Normal,
Optimize,
}
#[derive(Clone, Debug, PartialEq)]
pub enum MonoProblem {
PatternProblem(crate::exhaustive::Error),

View file

@ -6,6 +6,7 @@ use roc_can::def::Def;
use roc_collections::all::{MutMap, MutSet};
use roc_gen::llvm::externs::add_default_roc_externs;
use roc_module::symbol::Symbol;
use roc_mono::ir::OptLevel;
use roc_types::subs::VarStore;
fn promote_expr_to_module(src: &str) -> String {
@ -190,9 +191,9 @@ pub fn helper<'a>(
module.strip_debug_info();
let opt_level = if cfg!(debug_assertions) {
roc_gen::llvm::build::OptLevel::Normal
OptLevel::Normal
} else {
roc_gen::llvm::build::OptLevel::Optimize
OptLevel::Optimize
};
let module = arena.alloc(module);