mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-19 23:07:33 +03:00
Merge remote-tracking branch 'origin' into dev-backend
This commit is contained in:
commit
0cee2cd96f
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1222,25 +1222,25 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "inkwell"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release2#d0a1ce5e678cf0f0e62b757760d1019301c603c9"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release3#57e9f00d98fc99486e737c314e56a59498c5dbbb"
|
||||
dependencies = [
|
||||
"either",
|
||||
"inkwell_internals",
|
||||
"libc",
|
||||
"llvm-sys",
|
||||
"once_cell",
|
||||
"parking_lot 0.10.2",
|
||||
"parking_lot 0.11.0",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inkwell_internals"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release2#d0a1ce5e678cf0f0e62b757760d1019301c603c9"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release3#57e9f00d98fc99486e737c314e56a59498c5dbbb"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
"quote 0.6.13",
|
||||
"syn 0.15.44",
|
||||
"proc-macro2 1.0.21",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -77,7 +77,7 @@ libloading = "0.6"
|
||||
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
|
||||
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
|
||||
# This way, GitHub Actions works and nobody's builds get broken.
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
|
||||
target-lexicon = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
|
693
cli/src/repl.rs
693
cli/src/repl.rs
@ -1,32 +1,7 @@
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use roc_build::link::module_to_dylib;
|
||||
use roc_builtins::unique::uniq_stdlib;
|
||||
use roc_can::constraint::Constraint;
|
||||
use roc_can::expected::Expected;
|
||||
use roc_can::expr::{canonicalize_expr, Expr, Output};
|
||||
use roc_can::operator;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap, SendSet};
|
||||
use roc_constrain::expr::constrain_expr;
|
||||
use roc_constrain::module::{constrain_imported_values, Import};
|
||||
use roc_fmt::annotation::{Formattable, Newlines, Parens};
|
||||
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_parse::ast::{self, Attempting};
|
||||
use roc_parse::blankspace::space0_before;
|
||||
use roc_parse::parser::{loc, Fail, FailReason, Parser, State};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_solve::solve;
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
use roc_types::subs::{Content, Subs, VarStore, Variable};
|
||||
use roc_types::types::Type;
|
||||
use std::hash::Hash;
|
||||
use gen::{gen, ReplOutput};
|
||||
use roc_gen::llvm::build::OptLevel;
|
||||
use roc_parse::parser::{Fail, FailReason};
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8_unchecked;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub const WELCOME_MESSAGE: &str = "\n The rockin’ \u{001b}[36mroc repl\u{001b}[0m\n\u{001b}[35m────────────────────────\u{001b}[0m\n\n";
|
||||
@ -35,6 +10,7 @@ pub const PROMPT: &str = "\n\u{001b}[36m»\u{001b}[0m ";
|
||||
pub const ELLIPSIS: &str = "\u{001b}[36m…\u{001b}[0m ";
|
||||
|
||||
mod eval;
|
||||
mod gen;
|
||||
|
||||
pub fn main() -> io::Result<()> {
|
||||
use std::io::BufRead;
|
||||
@ -75,7 +51,7 @@ pub fn main() -> io::Result<()> {
|
||||
} else if prev_line_blank {
|
||||
// After two blank lines in a row, give up and try parsing it
|
||||
// even though it's going to fail. This way you don't get stuck.
|
||||
match print_output(pending_src.as_str()) {
|
||||
match eval_and_format(pending_src.as_str()) {
|
||||
Ok(output) => {
|
||||
println!("{}", output);
|
||||
}
|
||||
@ -97,12 +73,12 @@ pub fn main() -> io::Result<()> {
|
||||
}
|
||||
_ => {
|
||||
let result = if pending_src.is_empty() {
|
||||
print_output(line)
|
||||
eval_and_format(line)
|
||||
} else {
|
||||
pending_src.push('\n');
|
||||
pending_src.push_str(line);
|
||||
|
||||
print_output(pending_src.as_str())
|
||||
eval_and_format(pending_src.as_str())
|
||||
};
|
||||
|
||||
match result {
|
||||
@ -117,7 +93,7 @@ pub fn main() -> io::Result<()> {
|
||||
// If we hit an eof, and we're allowed to keep going,
|
||||
// append the str to the src we're building up and continue.
|
||||
// (We only need to append it here if it was empty before;
|
||||
// otherwise, we already appended it before calling print_output.)
|
||||
// otherwise, we already appended it before calling eval_and_format.)
|
||||
|
||||
if pending_src.is_empty() {
|
||||
pending_src.push_str(line);
|
||||
@ -141,7 +117,7 @@ fn report_parse_error(fail: Fail) {
|
||||
println!("TODO Gracefully report parse error in repl: {:?}", fail);
|
||||
}
|
||||
|
||||
fn print_output(src: &str) -> Result<String, Fail> {
|
||||
fn eval_and_format(src: &str) -> Result<String, Fail> {
|
||||
gen(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
|
||||
ReplOutput::NoProblems { expr, expr_type } => {
|
||||
format!("\n{} \u{001b}[35m:\u{001b}[0m {}", expr, expr_type)
|
||||
@ -149,654 +125,3 @@ fn print_output(src: &str) -> Result<String, Fail> {
|
||||
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn repl_home() -> ModuleId {
|
||||
ModuleIds::default().get_or_insert(&"REPL".into())
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
buffer.push_str(" ");
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||
};
|
||||
|
||||
let arena = Bump::new();
|
||||
|
||||
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
|
||||
let src_str: &str = unsafe { from_utf8_unchecked(src) };
|
||||
|
||||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
let stdlib_mode = stdlib.mode;
|
||||
let filename = PathBuf::from("REPL.roc");
|
||||
let src_dir = Path::new("fake/test/path");
|
||||
|
||||
let module_src = promote_expr_to_module(src_str);
|
||||
|
||||
let exposed_types = MutMap::default();
|
||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||
&arena,
|
||||
filename,
|
||||
&module_src,
|
||||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
);
|
||||
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
mut procedures,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
mut subs,
|
||||
module_id: home,
|
||||
sources,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for (home, (module_path, src)) in sources {
|
||||
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
|
||||
|
||||
if error_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
for problem in can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if !lines.is_empty() {
|
||||
Ok(ReplOutput::Problems(lines))
|
||||
} else {
|
||||
let context = Context::create();
|
||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
|
||||
let builder = context.create_builder();
|
||||
|
||||
debug_assert_eq!(exposed_to_host.len(), 1);
|
||||
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
|
||||
let main_fn_symbol = *main_fn_symbol;
|
||||
let main_fn_var = *main_fn_var;
|
||||
|
||||
// pretty-print the expr type string for later.
|
||||
name_all_type_vars(main_fn_var, &mut subs);
|
||||
let content = subs.get(main_fn_var).content;
|
||||
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
||||
|
||||
let (_, main_fn_layout) = procedures
|
||||
.keys()
|
||||
.find(|(s, _)| *s == main_fn_symbol)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
|
||||
let module = arena.alloc(module);
|
||||
let (module_pass, function_pass) =
|
||||
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let env = roc_gen::llvm::build::Env {
|
||||
arena: &arena,
|
||||
builder: &builder,
|
||||
context: &context,
|
||||
interns,
|
||||
module,
|
||||
ptr_bytes,
|
||||
leak: false,
|
||||
// important! we don't want any procedures to get the C calling convention
|
||||
exposed_to_host: MutSet::default(),
|
||||
};
|
||||
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let mut headers = Vec::with_capacity(procedures.len());
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
let mut scope = roc_gen::llvm::build::Scope::default();
|
||||
for ((symbol, layout), proc) in procedures.drain() {
|
||||
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||
|
||||
if proc.args.is_empty() {
|
||||
// this is a 0-argument thunk, i.e. a top-level constant definition
|
||||
// it must be in-scope everywhere in the module!
|
||||
scope.insert_top_level_thunk(symbol, layout, fn_val);
|
||||
}
|
||||
|
||||
headers.push((proc, fn_val));
|
||||
}
|
||||
|
||||
// Build each proc using its header info.
|
||||
for (proc, fn_val) in headers {
|
||||
let mut current_scope = scope.clone();
|
||||
|
||||
// only have top-level thunks for this proc's module in scope
|
||||
// this retain is not needed for correctness, but will cause less confusion when debugging
|
||||
let home = proc.name.module_id();
|
||||
current_scope.retain_top_level_thunks_for_module(home);
|
||||
|
||||
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
|
||||
|
||||
if fn_val.verify(true) {
|
||||
function_pass.run_on(&fn_val);
|
||||
} else {
|
||||
use roc_builtins::std::Mode;
|
||||
|
||||
let mode = match stdlib_mode {
|
||||
Mode::Uniqueness => "OPTIMIZED",
|
||||
Mode::Standard => "NON-OPTIMIZED",
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
|
||||
fn_val.get_name().to_str().unwrap(),
|
||||
mode,
|
||||
);
|
||||
|
||||
fn_val.print_to_stderr();
|
||||
|
||||
panic!(
|
||||
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
|
||||
fn_val.get_name().to_str().unwrap(),
|
||||
mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
|
||||
&env,
|
||||
&mut layout_ids,
|
||||
main_fn_symbol,
|
||||
&main_fn_layout,
|
||||
);
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
if main_fn.verify(true) {
|
||||
function_pass.run_on(&main_fn);
|
||||
} else {
|
||||
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
|
||||
}
|
||||
|
||||
module_pass.run_on(env.module);
|
||||
|
||||
// Verify the module
|
||||
if let Err(errors) = env.module.verify() {
|
||||
panic!("Errors defining module: {:?}", errors);
|
||||
}
|
||||
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
let lib = module_to_dylib(&env.module, &target, opt_level)
|
||||
.expect("Error loading compiled dylib for test");
|
||||
let answer = unsafe {
|
||||
eval::jit_to_ast(
|
||||
&arena,
|
||||
lib,
|
||||
main_fn_name,
|
||||
&main_fn_layout,
|
||||
&content,
|
||||
&env.interns,
|
||||
home,
|
||||
&subs,
|
||||
ptr_bytes,
|
||||
)
|
||||
};
|
||||
let mut expr = bumpalo::collections::String::new_in(&arena);
|
||||
|
||||
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
Ok(ReplOutput::NoProblems {
|
||||
expr: expr.into_bump_str().to_string(),
|
||||
expr_type: expr_type_str,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum ReplOutput {
|
||||
Problems(Vec<String>),
|
||||
NoProblems { expr: String, expr_type: String },
|
||||
}
|
||||
|
||||
pub fn infer_expr(
|
||||
subs: Subs,
|
||||
problems: &mut Vec<solve::TypeError>,
|
||||
constraint: &Constraint,
|
||||
expr_var: Variable,
|
||||
) -> (Content, Subs) {
|
||||
let env = solve::Env {
|
||||
aliases: MutMap::default(),
|
||||
vars_by_symbol: SendMap::default(),
|
||||
};
|
||||
let (solved, _) = solve::run(&env, problems, subs, constraint);
|
||||
|
||||
let content = solved.inner().get_without_compacting(expr_var).content;
|
||||
|
||||
(content, solved.into_inner())
|
||||
}
|
||||
|
||||
pub fn parse_loc_with<'a>(
|
||||
arena: &'a Bump,
|
||||
bytes: &'a [u8],
|
||||
) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||
let state = State::new(&bytes, Attempting::Module);
|
||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||
let answer = parser.parse(&arena, state);
|
||||
|
||||
answer
|
||||
.map(|(loc_expr, _)| loc_expr)
|
||||
.map_err(|(fail, _)| fail)
|
||||
}
|
||||
|
||||
pub fn can_expr(expr_bytes: &[u8]) -> Result<CanExprOut, Fail> {
|
||||
can_expr_with(&Bump::new(), repl_home(), expr_bytes)
|
||||
}
|
||||
|
||||
// TODO make this return a named struct instead of a big tuple
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn uniq_expr(
|
||||
expr_bytes: &[u8],
|
||||
) -> Result<
|
||||
(
|
||||
Located<roc_can::expr::Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
),
|
||||
Fail,
|
||||
> {
|
||||
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
|
||||
|
||||
uniq_expr_with(&Bump::new(), expr_bytes, declared_idents)
|
||||
}
|
||||
|
||||
// TODO make this return a named struct instead of a big tuple
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn uniq_expr_with(
|
||||
arena: &Bump,
|
||||
expr_bytes: &[u8],
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
) -> Result<
|
||||
(
|
||||
Located<roc_can::expr::Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
),
|
||||
Fail,
|
||||
> {
|
||||
let home = repl_home();
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
problems,
|
||||
var_store: mut old_var_store,
|
||||
var,
|
||||
interns,
|
||||
..
|
||||
} = can_expr_with(arena, home, expr_bytes)?;
|
||||
|
||||
// double check
|
||||
let mut var_store = VarStore::new(old_var_store.fresh());
|
||||
|
||||
let expected2 = Expected::NoExpectation(Type::Variable(var));
|
||||
let constraint = roc_constrain::uniq::constrain_declaration(
|
||||
home,
|
||||
&mut var_store,
|
||||
Region::zero(),
|
||||
&loc_expr,
|
||||
declared_idents,
|
||||
expected2,
|
||||
);
|
||||
|
||||
let stdlib = uniq_stdlib();
|
||||
|
||||
let types = stdlib.types;
|
||||
let imports: Vec<_> = types
|
||||
.into_iter()
|
||||
.map(|(symbol, (solved_type, region))| Import {
|
||||
loc_symbol: Located::at(region, symbol),
|
||||
solved_type,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// load builtin values
|
||||
|
||||
// TODO what to do with those rigids?
|
||||
let (_introduced_rigids, constraint) =
|
||||
constrain_imported_values(imports, constraint, &mut var_store);
|
||||
|
||||
let subs2 = Subs::new(var_store.into());
|
||||
|
||||
Ok((
|
||||
loc_expr, output, problems, subs2, var, constraint, home, interns,
|
||||
))
|
||||
}
|
||||
|
||||
pub struct CanExprOut {
|
||||
pub loc_expr: Located<roc_can::expr::Expr>,
|
||||
pub output: Output,
|
||||
pub problems: Vec<Problem>,
|
||||
pub home: ModuleId,
|
||||
pub interns: Interns,
|
||||
pub var_store: VarStore,
|
||||
pub var: Variable,
|
||||
pub constraint: Constraint,
|
||||
}
|
||||
|
||||
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_bytes: &[u8]) -> Result<CanExprOut, Fail> {
|
||||
let loc_expr = parse_loc_with(&arena, expr_bytes)?;
|
||||
let mut var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
let expected = Expected::NoExpectation(Type::Variable(var));
|
||||
let module_ids = ModuleIds::default();
|
||||
|
||||
// Desugar operators (convert them to Apply calls, taking into account
|
||||
// operator precedence and associativity rules), before doing other canonicalization.
|
||||
//
|
||||
// If we did this *during* canonicalization, then each time we
|
||||
// visited a BinOp node we'd recursively try to apply this to each of its nested
|
||||
// operators, and then again on *their* nested operators, ultimately applying the
|
||||
// rules multiple times unnecessarily.
|
||||
let loc_expr = operator::desugar_expr(arena, &loc_expr);
|
||||
|
||||
let mut scope = Scope::new(home, &mut var_store);
|
||||
let dep_idents = IdentIds::exposed_builtins(0);
|
||||
let mut env = roc_can::env::Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
||||
let (loc_expr, output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&mut var_store,
|
||||
&mut scope,
|
||||
Region::zero(),
|
||||
&loc_expr.value,
|
||||
);
|
||||
|
||||
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
|
||||
// since we aren't using modules here.
|
||||
let mut with_builtins = loc_expr.value;
|
||||
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
|
||||
|
||||
for (symbol, def) in builtin_defs {
|
||||
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
|
||||
{
|
||||
with_builtins = Expr::LetNonRec(
|
||||
Box::new(def),
|
||||
Box::new(Located {
|
||||
region: Region::zero(),
|
||||
value: with_builtins,
|
||||
}),
|
||||
var_store.fresh(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let loc_expr = Located {
|
||||
region: loc_expr.region,
|
||||
value: with_builtins,
|
||||
};
|
||||
|
||||
let constraint = constrain_expr(
|
||||
&roc_constrain::expr::Env {
|
||||
rigids: ImMap::default(),
|
||||
home,
|
||||
},
|
||||
loc_expr.region,
|
||||
&loc_expr.value,
|
||||
expected,
|
||||
);
|
||||
|
||||
let types = roc_builtins::std::types();
|
||||
|
||||
let imports: Vec<_> = types
|
||||
.into_iter()
|
||||
.map(|(symbol, (solved_type, region))| Import {
|
||||
loc_symbol: Located::at(region, symbol),
|
||||
solved_type,
|
||||
})
|
||||
.collect();
|
||||
|
||||
//load builtin values
|
||||
let (_introduced_rigids, constraint) =
|
||||
constrain_imported_values(imports, constraint, &mut var_store);
|
||||
|
||||
// TODO determine what to do with those rigids
|
||||
// for var in introduced_rigids {
|
||||
// output.ftv.insert(var, format!("internal_{:?}", var).into());
|
||||
// }
|
||||
|
||||
let mut all_ident_ids = MutMap::default();
|
||||
|
||||
// When pretty printing types, we may need the exposed builtins,
|
||||
// so include them in the Interns we'll ultimately return.
|
||||
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
|
||||
all_ident_ids.insert(module_id, ident_ids);
|
||||
}
|
||||
|
||||
all_ident_ids.insert(home, env.ident_ids);
|
||||
|
||||
let interns = Interns {
|
||||
module_ids: env.module_ids.clone(),
|
||||
all_ident_ids,
|
||||
};
|
||||
|
||||
Ok(CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
problems: env.problems,
|
||||
home: env.home,
|
||||
var_store,
|
||||
interns,
|
||||
var,
|
||||
constraint,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Hash + Eq,
|
||||
{
|
||||
let mut answer = MutMap::default();
|
||||
|
||||
for (key, value) in pairs {
|
||||
answer.insert(key, value);
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
pub fn im_map_from_pairs<K, V, I>(pairs: I) -> ImMap<K, V>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Hash + Eq + Clone,
|
||||
V: Clone,
|
||||
{
|
||||
let mut answer = ImMap::default();
|
||||
|
||||
for (key, value) in pairs {
|
||||
answer.insert(key, value);
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
pub fn send_set_from<V, I>(elems: I) -> SendSet<V>
|
||||
where
|
||||
I: IntoIterator<Item = V>,
|
||||
V: Hash + Eq + Clone,
|
||||
{
|
||||
let mut answer = SendSet::default();
|
||||
|
||||
for elem in elems {
|
||||
answer.insert(elem);
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
// Check constraints
|
||||
//
|
||||
// Keep track of the used (in types or expectations) variables, and the declared variables (in
|
||||
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
|
||||
// and no variables that are used but not declared are allowed.
|
||||
//
|
||||
// There is one exception: the initial variable (that stores the type of the whole expression) is
|
||||
// never declared, but is used.
|
||||
pub fn assert_correct_variable_usage(constraint: &Constraint) {
|
||||
// variables declared in constraint (flex_vars or rigid_vars)
|
||||
// and variables actually used in constraints
|
||||
let (declared, used) = variable_usage(constraint);
|
||||
|
||||
let used: ImSet<Variable> = used.into();
|
||||
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
|
||||
|
||||
for var in declared.flex_vars.clone() {
|
||||
decl.insert(var);
|
||||
}
|
||||
|
||||
let diff = used.clone().relative_complement(decl);
|
||||
|
||||
// NOTE: this checks whether we're using variables that are not declared. For recursive type
|
||||
// definitions, their rigid types are declared twice, which is correct!
|
||||
if !diff.is_empty() {
|
||||
println!("VARIABLE USAGE PROBLEM");
|
||||
|
||||
println!("used: {:?}", &used);
|
||||
println!("rigids: {:?}", &declared.rigid_vars);
|
||||
println!("flexs: {:?}", &declared.flex_vars);
|
||||
|
||||
println!("difference: {:?}", &diff);
|
||||
|
||||
panic!("variable usage problem (see stdout for details)");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SeenVariables {
|
||||
pub rigid_vars: Vec<Variable>,
|
||||
pub flex_vars: Vec<Variable>,
|
||||
}
|
||||
|
||||
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
|
||||
let mut declared = SeenVariables::default();
|
||||
let mut used = ImSet::default();
|
||||
variable_usage_help(con, &mut declared, &mut used);
|
||||
|
||||
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
|
||||
|
||||
let mut used_vec: Vec<Variable> = used.into_iter().collect();
|
||||
used_vec.sort();
|
||||
|
||||
declared.rigid_vars.sort();
|
||||
declared.flex_vars.sort();
|
||||
|
||||
(declared, used_vec)
|
||||
}
|
||||
|
||||
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
|
||||
use Constraint::*;
|
||||
|
||||
match con {
|
||||
True | SaveTheEnvironment => (),
|
||||
Eq(tipe, expectation, _, _) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
for v in expectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Store(tipe, var, _, _) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
used.insert(*var);
|
||||
}
|
||||
Lookup(_, expectation, _) => {
|
||||
for v in expectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Pattern(_, _, tipe, pexpectation) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
for v in pexpectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Let(letcon) => {
|
||||
declared.rigid_vars.extend(letcon.rigid_vars.clone());
|
||||
declared.flex_vars.extend(letcon.flex_vars.clone());
|
||||
|
||||
variable_usage_help(&letcon.defs_constraint, declared, used);
|
||||
variable_usage_help(&letcon.ret_constraint, declared, used);
|
||||
}
|
||||
And(constraints) => {
|
||||
for sub in constraints {
|
||||
variable_usage_help(sub, declared, used);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
109
cli/src/repl/debug.rs
Normal file
109
cli/src/repl/debug.rs
Normal file
@ -0,0 +1,109 @@
|
||||
|
||||
// Check constraints
|
||||
//
|
||||
// Keep track of the used (in types or expectations) variables, and the declared variables (in
|
||||
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
|
||||
// and no variables that are used but not declared are allowed.
|
||||
//
|
||||
// There is one exception: the initial variable (that stores the type of the whole expression) is
|
||||
// never declared, but is used.
|
||||
pub fn assert_correct_variable_usage(constraint: &Constraint) {
|
||||
// variables declared in constraint (flex_vars or rigid_vars)
|
||||
// and variables actually used in constraints
|
||||
let (declared, used) = variable_usage(constraint);
|
||||
|
||||
let used: ImSet<Variable> = used.into();
|
||||
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
|
||||
|
||||
for var in declared.flex_vars.clone() {
|
||||
decl.insert(var);
|
||||
}
|
||||
|
||||
let diff = used.clone().relative_complement(decl);
|
||||
|
||||
// NOTE: this checks whether we're using variables that are not declared. For recursive type
|
||||
// definitions, their rigid types are declared twice, which is correct!
|
||||
if !diff.is_empty() {
|
||||
println!("VARIABLE USAGE PROBLEM");
|
||||
|
||||
println!("used: {:?}", &used);
|
||||
println!("rigids: {:?}", &declared.rigid_vars);
|
||||
println!("flexs: {:?}", &declared.flex_vars);
|
||||
|
||||
println!("difference: {:?}", &diff);
|
||||
|
||||
panic!("variable usage problem (see stdout for details)");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SeenVariables {
|
||||
pub rigid_vars: Vec<Variable>,
|
||||
pub flex_vars: Vec<Variable>,
|
||||
}
|
||||
|
||||
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
|
||||
let mut declared = SeenVariables::default();
|
||||
let mut used = ImSet::default();
|
||||
variable_usage_help(con, &mut declared, &mut used);
|
||||
|
||||
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
|
||||
|
||||
let mut used_vec: Vec<Variable> = used.into_iter().collect();
|
||||
used_vec.sort();
|
||||
|
||||
declared.rigid_vars.sort();
|
||||
declared.flex_vars.sort();
|
||||
|
||||
(declared, used_vec)
|
||||
}
|
||||
|
||||
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
|
||||
use Constraint::*;
|
||||
|
||||
match con {
|
||||
True | SaveTheEnvironment => (),
|
||||
Eq(tipe, expectation, _, _) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
for v in expectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Store(tipe, var, _, _) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
used.insert(*var);
|
||||
}
|
||||
Lookup(_, expectation, _) => {
|
||||
for v in expectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Pattern(_, _, tipe, pexpectation) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
for v in pexpectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Let(letcon) => {
|
||||
declared.rigid_vars.extend(letcon.rigid_vars.clone());
|
||||
declared.flex_vars.extend(letcon.flex_vars.clone());
|
||||
|
||||
variable_usage_help(&letcon.defs_constraint, declared, used);
|
||||
variable_usage_help(&letcon.ret_constraint, declared, used);
|
||||
}
|
||||
And(constraints) => {
|
||||
for sub in constraints {
|
||||
variable_usage_help(sub, declared, used);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
276
cli/src/repl/gen.rs
Normal file
276
cli/src/repl/gen.rs
Normal file
@ -0,0 +1,276 @@
|
||||
use crate::repl::eval;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use roc_build::link::module_to_dylib;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_fmt::annotation::Formattable;
|
||||
use roc_fmt::annotation::{Newlines, Parens};
|
||||
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
||||
use roc_parse::parser::Fail;
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8_unchecked;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub enum ReplOutput {
|
||||
Problems(Vec<String>),
|
||||
NoProblems { expr: String, expr_type: String },
|
||||
}
|
||||
|
||||
pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||
};
|
||||
|
||||
let arena = Bump::new();
|
||||
|
||||
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
|
||||
let src_str: &str = unsafe { from_utf8_unchecked(src) };
|
||||
|
||||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
let stdlib_mode = stdlib.mode;
|
||||
let filename = PathBuf::from("REPL.roc");
|
||||
let src_dir = Path::new("fake/test/path");
|
||||
|
||||
let module_src = promote_expr_to_module(src_str);
|
||||
|
||||
let exposed_types = MutMap::default();
|
||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||
&arena,
|
||||
filename,
|
||||
&module_src,
|
||||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
);
|
||||
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
mut procedures,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
mut subs,
|
||||
module_id: home,
|
||||
sources,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for (home, (module_path, src)) in sources {
|
||||
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
|
||||
|
||||
if error_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
for problem in can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if !lines.is_empty() {
|
||||
Ok(ReplOutput::Problems(lines))
|
||||
} else {
|
||||
let context = Context::create();
|
||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
|
||||
let builder = context.create_builder();
|
||||
|
||||
debug_assert_eq!(exposed_to_host.len(), 1);
|
||||
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
|
||||
let main_fn_symbol = *main_fn_symbol;
|
||||
let main_fn_var = *main_fn_var;
|
||||
|
||||
// pretty-print the expr type string for later.
|
||||
name_all_type_vars(main_fn_var, &mut subs);
|
||||
let content = subs.get(main_fn_var).content;
|
||||
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
||||
|
||||
let (_, main_fn_layout) = procedures
|
||||
.keys()
|
||||
.find(|(s, _)| *s == main_fn_symbol)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
|
||||
let module = arena.alloc(module);
|
||||
let (module_pass, function_pass) =
|
||||
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
||||
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let env = roc_gen::llvm::build::Env {
|
||||
arena: &arena,
|
||||
builder: &builder,
|
||||
dibuilder: &dibuilder,
|
||||
compile_unit: &compile_unit,
|
||||
context: &context,
|
||||
interns,
|
||||
module,
|
||||
ptr_bytes,
|
||||
leak: false,
|
||||
// important! we don't want any procedures to get the C calling convention
|
||||
exposed_to_host: MutSet::default(),
|
||||
};
|
||||
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let mut headers = Vec::with_capacity(procedures.len());
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
let mut scope = roc_gen::llvm::build::Scope::default();
|
||||
for ((symbol, layout), proc) in procedures.drain() {
|
||||
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||
|
||||
if proc.args.is_empty() {
|
||||
// this is a 0-argument thunk, i.e. a top-level constant definition
|
||||
// it must be in-scope everywhere in the module!
|
||||
scope.insert_top_level_thunk(symbol, layout, fn_val);
|
||||
}
|
||||
|
||||
headers.push((proc, fn_val));
|
||||
}
|
||||
|
||||
// Build each proc using its header info.
|
||||
for (proc, fn_val) in headers {
|
||||
let mut current_scope = scope.clone();
|
||||
|
||||
// only have top-level thunks for this proc's module in scope
|
||||
// this retain is not needed for correctness, but will cause less confusion when debugging
|
||||
let home = proc.name.module_id();
|
||||
current_scope.retain_top_level_thunks_for_module(home);
|
||||
|
||||
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
|
||||
|
||||
// call finalize() before any code generation/verification
|
||||
env.dibuilder.finalize();
|
||||
|
||||
if fn_val.verify(true) {
|
||||
function_pass.run_on(&fn_val);
|
||||
} else {
|
||||
use roc_builtins::std::Mode;
|
||||
|
||||
let mode = match stdlib_mode {
|
||||
Mode::Uniqueness => "OPTIMIZED",
|
||||
Mode::Standard => "NON-OPTIMIZED",
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
|
||||
fn_val.get_name().to_str().unwrap(),
|
||||
mode,
|
||||
);
|
||||
|
||||
fn_val.print_to_stderr();
|
||||
|
||||
panic!(
|
||||
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
|
||||
fn_val.get_name().to_str().unwrap(),
|
||||
mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
|
||||
&env,
|
||||
&mut layout_ids,
|
||||
main_fn_symbol,
|
||||
&main_fn_layout,
|
||||
);
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
if main_fn.verify(true) {
|
||||
function_pass.run_on(&main_fn);
|
||||
} else {
|
||||
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
|
||||
}
|
||||
|
||||
module_pass.run_on(env.module);
|
||||
|
||||
// Verify the module
|
||||
if let Err(errors) = env.module.verify() {
|
||||
panic!("Errors defining module: {:?}", errors);
|
||||
}
|
||||
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
let lib = module_to_dylib(&env.module, &target, opt_level)
|
||||
.expect("Error loading compiled dylib for test");
|
||||
let answer = unsafe {
|
||||
eval::jit_to_ast(
|
||||
&arena,
|
||||
lib,
|
||||
main_fn_name,
|
||||
&main_fn_layout,
|
||||
&content,
|
||||
&env.interns,
|
||||
home,
|
||||
&subs,
|
||||
ptr_bytes,
|
||||
)
|
||||
};
|
||||
let mut expr = bumpalo::collections::String::new_in(&arena);
|
||||
|
||||
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
Ok(ReplOutput::NoProblems {
|
||||
expr: expr.into_bump_str().to_string(),
|
||||
expr_type: expr_type_str,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
buffer.push_str(" ");
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
@ -45,7 +45,7 @@ tempfile = "3.1.0"
|
||||
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
|
||||
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
|
||||
# This way, GitHub Actions works and nobody's builds get broken.
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
|
||||
target-lexicon = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -66,7 +66,12 @@ pub fn gen_from_mono_module(
|
||||
|
||||
let context = Context::create();
|
||||
let module = arena.alloc(module_from_builtins(&context, "app"));
|
||||
|
||||
// strip Zig debug stuff
|
||||
// module.strip_debug_info();
|
||||
|
||||
let builder = context.create_builder();
|
||||
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
|
||||
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
@ -75,6 +80,8 @@ pub fn gen_from_mono_module(
|
||||
let env = roc_gen::llvm::build::Env {
|
||||
arena: &arena,
|
||||
builder: &builder,
|
||||
dibuilder: &dibuilder,
|
||||
compile_unit: &compile_unit,
|
||||
context: &context,
|
||||
interns: loaded.interns,
|
||||
module,
|
||||
@ -112,6 +119,9 @@ pub fn gen_from_mono_module(
|
||||
// println!("\n\nBuilding and then verifying function {:?}\n\n", proc);
|
||||
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
|
||||
|
||||
// call finalize() before any code generation/verification
|
||||
env.dibuilder.finalize();
|
||||
|
||||
if fn_val.verify(true) {
|
||||
fpm.run_on(&fn_val);
|
||||
} else {
|
||||
@ -124,6 +134,8 @@ pub fn gen_from_mono_module(
|
||||
}
|
||||
}
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
|
@ -197,7 +197,7 @@ pub fn canonicalize_pattern<'a>(
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
Underscore => match pattern_type {
|
||||
Underscore(_) => match pattern_type {
|
||||
WhenBranch | FunctionArg => Pattern::Underscore,
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
@ -37,7 +37,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
|
||||
| Pattern::NonBase10Literal { .. }
|
||||
| Pattern::FloatLiteral(_)
|
||||
| Pattern::StrLiteral(_)
|
||||
| Pattern::Underscore
|
||||
| Pattern::Underscore(_)
|
||||
| Pattern::Malformed(_)
|
||||
| Pattern::QualifiedIdentifier { .. } => false,
|
||||
}
|
||||
@ -128,7 +128,10 @@ impl<'a> Formattable<'a> for Pattern<'a> {
|
||||
StrLiteral(literal) => {
|
||||
todo!("Format string literal: {:?}", literal);
|
||||
}
|
||||
Underscore => buf.push('_'),
|
||||
Underscore(name) => {
|
||||
buf.push('_');
|
||||
buf.push_str(name);
|
||||
}
|
||||
|
||||
// Space
|
||||
SpaceBefore(sub_pattern, spaces) => {
|
||||
|
@ -39,7 +39,7 @@ either = "1.6.1"
|
||||
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
|
||||
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
|
||||
# This way, GitHub Actions works and nobody's builds get broken.
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
|
||||
target-lexicon = "0.10"
|
||||
libloading = "0.6"
|
||||
|
||||
|
@ -9,14 +9,15 @@ use crate::llvm::convert::{
|
||||
basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int,
|
||||
};
|
||||
use crate::llvm::refcounting::{
|
||||
decrement_refcount_layout, increment_refcount_layout, list_get_refcount_ptr,
|
||||
refcount_is_one_comparison,
|
||||
decrement_refcount_layout, increment_refcount_layout, refcount_is_one_comparison,
|
||||
PointerToRefcount,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::debug_info::{AsDIScope, DICompileUnit, DISubprogram, DebugInfoBuilder};
|
||||
use inkwell::memory_buffer::MemoryBuffer;
|
||||
use inkwell::module::{Linkage, Module};
|
||||
use inkwell::passes::{PassManager, PassManagerBuilder};
|
||||
@ -96,6 +97,8 @@ pub struct Env<'a, 'ctx, 'env> {
|
||||
pub arena: &'a Bump,
|
||||
pub context: &'ctx Context,
|
||||
pub builder: &'env Builder<'ctx>,
|
||||
pub dibuilder: &'env DebugInfoBuilder<'ctx>,
|
||||
pub compile_unit: &'env DICompileUnit<'ctx>,
|
||||
pub module: &'ctx Module<'ctx>,
|
||||
pub interns: Interns,
|
||||
pub ptr_bytes: u32,
|
||||
@ -178,6 +181,61 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_debug_info(module: &Module<'ctx>) -> (DebugInfoBuilder<'ctx>, DICompileUnit<'ctx>) {
|
||||
module.create_debug_info_builder(
|
||||
true,
|
||||
/* language */ inkwell::debug_info::DWARFSourceLanguage::C,
|
||||
/* filename */ "roc_app",
|
||||
/* directory */ ".",
|
||||
/* producer */ "my llvm compiler frontend",
|
||||
/* is_optimized */ false,
|
||||
/* compiler command line flags */ "",
|
||||
/* runtime_ver */ 0,
|
||||
/* split_name */ "",
|
||||
/* kind */ inkwell::debug_info::DWARFEmissionKind::Full,
|
||||
/* dwo_id */ 0,
|
||||
/* split_debug_inling */ false,
|
||||
/* debug_info_for_profiling */ false,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_subprogram(&self, function_name: &str) -> DISubprogram<'ctx> {
|
||||
use inkwell::debug_info::DIFlagsConstants;
|
||||
|
||||
let dibuilder = self.dibuilder;
|
||||
let compile_unit = self.compile_unit;
|
||||
|
||||
let ditype = dibuilder
|
||||
.create_basic_type(
|
||||
"type_name",
|
||||
0_u64,
|
||||
0x00,
|
||||
inkwell::debug_info::DIFlags::PUBLIC,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let subroutine_type = dibuilder.create_subroutine_type(
|
||||
compile_unit.get_file(),
|
||||
/* return type */ Some(ditype.as_type()),
|
||||
/* parameter types */ &[],
|
||||
inkwell::debug_info::DIFlags::PUBLIC,
|
||||
);
|
||||
|
||||
dibuilder.create_function(
|
||||
/* scope */ compile_unit.as_debug_info_scope(),
|
||||
/* func name */ function_name,
|
||||
/* linkage_name */ None,
|
||||
/* file */ compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* DIType */ subroutine_type,
|
||||
/* is_local_to_unit */ true,
|
||||
/* is_definition */ true,
|
||||
/* scope_line */ 0,
|
||||
/* flags */ inkwell::debug_info::DIFlags::PUBLIC,
|
||||
/* is_optimized */ false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Module<'ctx> {
|
||||
@ -983,63 +1041,77 @@ pub fn allocate_with_refcount<'a, 'ctx, 'env>(
|
||||
let ctx = env.context;
|
||||
|
||||
let value_type = basic_type_from_layout(env.arena, ctx, layout, env.ptr_bytes);
|
||||
let value_bytes = layout.stack_size(env.ptr_bytes) as u64;
|
||||
let value_bytes = layout.stack_size(env.ptr_bytes);
|
||||
|
||||
let len_type = env.ptr_int();
|
||||
// bytes per element
|
||||
let bytes_len = len_type.const_int(value_bytes, false);
|
||||
|
||||
let offset = crate::llvm::refcounting::refcount_offset(env, layout);
|
||||
let extra_bytes = layout.alignment_bytes(env.ptr_bytes);
|
||||
|
||||
let ptr = {
|
||||
let len = bytes_len;
|
||||
let len =
|
||||
builder.build_int_add(len, len_type.const_int(offset, false), "add_refcount_space");
|
||||
let len = value_bytes as u64 + extra_bytes as u64;
|
||||
|
||||
// bytes per element
|
||||
let bytes_len = len_type.const_int(len, false);
|
||||
|
||||
env.builder
|
||||
.build_array_malloc(ctx.i8_type(), len, "create_list_ptr")
|
||||
.build_array_malloc(ctx.i8_type(), bytes_len, "create_ptr")
|
||||
.unwrap()
|
||||
|
||||
// TODO check if malloc returned null; if so, runtime error for OOM!
|
||||
};
|
||||
|
||||
// We must return a pointer to the first element:
|
||||
let ptr_bytes = env.ptr_bytes;
|
||||
let int_type = ptr_int(ctx, ptr_bytes);
|
||||
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "allocate_refcount_pti");
|
||||
let incremented = builder.build_int_add(
|
||||
ptr_as_int,
|
||||
ctx.i64_type().const_int(offset, false),
|
||||
"increment_list_ptr",
|
||||
);
|
||||
let data_ptr = {
|
||||
let int_type = ptr_int(ctx, env.ptr_bytes);
|
||||
let as_usize_ptr = cast_basic_basic(
|
||||
env.builder,
|
||||
ptr.into(),
|
||||
int_type.ptr_type(AddressSpace::Generic).into(),
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
let ptr_type = get_ptr_type(&value_type, AddressSpace::Generic);
|
||||
let list_element_ptr = builder.build_int_to_ptr(incremented, ptr_type, "allocate_refcount_itp");
|
||||
let index = match extra_bytes {
|
||||
n if n == env.ptr_bytes => 1,
|
||||
n if n == 2 * env.ptr_bytes => 2,
|
||||
_ => unreachable!("invalid extra_bytes"),
|
||||
};
|
||||
|
||||
// subtract ptr_size, to access the refcount
|
||||
let refcount_ptr = builder.build_int_sub(
|
||||
incremented,
|
||||
ctx.i64_type().const_int(env.ptr_bytes as u64, false),
|
||||
"refcount_ptr",
|
||||
);
|
||||
let index_intvalue = int_type.const_int(index, false);
|
||||
|
||||
let refcount_ptr = builder.build_int_to_ptr(
|
||||
refcount_ptr,
|
||||
int_type.ptr_type(AddressSpace::Generic),
|
||||
"make ptr",
|
||||
);
|
||||
let ptr_type = get_ptr_type(&value_type, AddressSpace::Generic);
|
||||
|
||||
// the refcount of a new allocation is initially 1
|
||||
// we assume that the allocation is indeed used (dead variables are eliminated)
|
||||
builder.build_store(
|
||||
refcount_ptr,
|
||||
crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes),
|
||||
);
|
||||
unsafe {
|
||||
cast_basic_basic(
|
||||
env.builder,
|
||||
env.builder
|
||||
.build_in_bounds_gep(as_usize_ptr, &[index_intvalue], "get_data_ptr")
|
||||
.into(),
|
||||
ptr_type.into(),
|
||||
)
|
||||
.into_pointer_value()
|
||||
}
|
||||
};
|
||||
|
||||
let refcount_ptr = match extra_bytes {
|
||||
n if n == env.ptr_bytes => {
|
||||
// the malloced pointer is the same as the refcounted pointer
|
||||
unsafe { PointerToRefcount::from_ptr(env, ptr) }
|
||||
}
|
||||
n if n == 2 * env.ptr_bytes => {
|
||||
// the refcount is stored just before the start of the actual data
|
||||
// but in this case (because of alignment) not at the start of the malloced buffer
|
||||
PointerToRefcount::from_ptr_to_data(env, data_ptr)
|
||||
}
|
||||
n => unreachable!("invalid extra_bytes {}", n),
|
||||
};
|
||||
|
||||
let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes);
|
||||
refcount_ptr.set_refcount(env, rc1);
|
||||
|
||||
// store the value in the pointer
|
||||
builder.build_store(list_element_ptr, value);
|
||||
builder.build_store(data_ptr, value);
|
||||
|
||||
list_element_ptr
|
||||
data_ptr
|
||||
}
|
||||
|
||||
fn list_literal<'a, 'ctx, 'env>(
|
||||
@ -1610,7 +1682,10 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
|
||||
) {
|
||||
let c_function_name: String = format!("{}_exposed", roc_function.get_name().to_str().unwrap());
|
||||
|
||||
expose_function_to_host_help(env, roc_function, &c_function_name);
|
||||
let result = expose_function_to_host_help(env, roc_function, &c_function_name);
|
||||
|
||||
let subprogram = env.new_subprogram(&c_function_name);
|
||||
result.set_subprogram(subprogram);
|
||||
}
|
||||
|
||||
fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||
@ -1636,6 +1711,9 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||
env.module
|
||||
.add_function(c_function_name, c_function_type, Some(Linkage::External));
|
||||
|
||||
let subprogram = env.new_subprogram(c_function_name);
|
||||
c_function.set_subprogram(subprogram);
|
||||
|
||||
// STEP 2: build the exposed function's body
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
@ -1644,6 +1722,23 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = c_function.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
env.context,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(env.context, loc);
|
||||
|
||||
// drop the final argument, which is the pointer we write the result into
|
||||
let args = c_function.get_params();
|
||||
let output_arg_index = args.len() - 1;
|
||||
@ -1955,6 +2050,9 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
|
||||
|
||||
fn_val.set_call_conventions(FAST_CALL_CONV);
|
||||
|
||||
let subprogram = env.new_subprogram(&fn_name);
|
||||
fn_val.set_subprogram(subprogram);
|
||||
|
||||
if env.exposed_to_host.contains(&symbol) {
|
||||
expose_function_to_host(env, fn_val);
|
||||
}
|
||||
@ -2094,6 +2192,24 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = fn_val.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
context,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
|
||||
builder.set_current_debug_location(&context, loc);
|
||||
|
||||
// Add args to scope
|
||||
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
|
||||
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
||||
@ -2654,12 +2770,8 @@ where
|
||||
|
||||
let ret_type = basic_type_from_layout(env.arena, ctx, list_layout, env.ptr_bytes);
|
||||
|
||||
let refcount_ptr = list_get_refcount_ptr(env, list_layout, original_wrapper);
|
||||
|
||||
let refcount = env
|
||||
.builder
|
||||
.build_load(refcount_ptr, "get_refcount")
|
||||
.into_int_value();
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
|
||||
let refcount = refcount_ptr.get_refcount(env);
|
||||
|
||||
let comparison = refcount_is_one_comparison(env, refcount);
|
||||
|
||||
|
@ -185,7 +185,9 @@ pub fn list_prepend<'a, 'ctx, 'env>(
|
||||
// one we just malloc'd.
|
||||
//
|
||||
// TODO how do we decide when to do the small memcpy vs the normal one?
|
||||
builder.build_memcpy(index_1_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size);
|
||||
builder
|
||||
.build_memcpy(index_1_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size)
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
|
||||
}
|
||||
@ -626,7 +628,9 @@ pub fn list_append<'a, 'ctx, 'env>(
|
||||
// one we just malloc'd.
|
||||
//
|
||||
// TODO how do we decide when to do the small memcpy vs the normal one?
|
||||
builder.build_memcpy(clone_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size);
|
||||
builder
|
||||
.build_memcpy(clone_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size)
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
|
||||
}
|
||||
@ -1816,7 +1820,9 @@ pub fn clone_nonempty_list<'a, 'ctx, 'env>(
|
||||
// one we just malloc'd.
|
||||
//
|
||||
// TODO how do we decide when to do the small memcpy vs the normal one?
|
||||
builder.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size);
|
||||
builder
|
||||
.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size)
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
|
||||
}
|
||||
@ -1872,7 +1878,9 @@ pub fn clone_list<'a, 'ctx, 'env>(
|
||||
);
|
||||
|
||||
// copy old elements in
|
||||
builder.build_memcpy(new_ptr, ptr_bytes, old_ptr, ptr_bytes, bytes);
|
||||
builder
|
||||
.build_memcpy(new_ptr, ptr_bytes, old_ptr, ptr_bytes, bytes)
|
||||
.unwrap();
|
||||
|
||||
new_ptr
|
||||
}
|
||||
|
@ -537,7 +537,9 @@ fn clone_nonempty_str<'a, 'ctx, 'env>(
|
||||
|
||||
// Copy the bytes from the original array into the new
|
||||
// one we just malloc'd.
|
||||
builder.build_memcpy(clone_ptr, ptr_bytes, bytes_ptr, ptr_bytes, len);
|
||||
builder
|
||||
.build_memcpy(clone_ptr, ptr_bytes, bytes_ptr, ptr_bytes, len)
|
||||
.unwrap();
|
||||
|
||||
// Create a fresh wrapper struct for the newly populated array
|
||||
let struct_type = collection(ctx, env.ptr_bytes);
|
||||
|
@ -5,8 +5,8 @@ use crate::llvm::build::{
|
||||
use crate::llvm::build_list::list_len;
|
||||
use crate::llvm::convert::{basic_type_from_layout, block_of_memory, ptr_int};
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::debug_info::AsDIScope;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
@ -28,6 +28,265 @@ pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PointerToRefcount<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx> PointerToRefcount<'ctx> {
|
||||
/// # Safety
|
||||
///
|
||||
/// the invariant is that the given pointer really points to the refcount,
|
||||
/// not the data, and only is the start of the malloced buffer if the alignment
|
||||
/// works out that way.
|
||||
pub unsafe fn from_ptr<'a, 'env>(env: &Env<'a, 'ctx, 'env>, ptr: PointerValue<'ctx>) -> Self {
|
||||
// must make sure it's a pointer to usize
|
||||
let refcount_type = ptr_int(env.context, env.ptr_bytes);
|
||||
|
||||
let value = cast_basic_basic(
|
||||
env.builder,
|
||||
ptr.into(),
|
||||
refcount_type.ptr_type(AddressSpace::Generic).into(),
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
Self { value }
|
||||
}
|
||||
|
||||
pub fn from_ptr_to_data<'a, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
data_ptr: PointerValue<'ctx>,
|
||||
) -> Self {
|
||||
let builder = env.builder;
|
||||
// pointer to usize
|
||||
let refcount_type = ptr_int(env.context, env.ptr_bytes);
|
||||
|
||||
let ptr_as_usize_ptr = cast_basic_basic(
|
||||
builder,
|
||||
data_ptr.into(),
|
||||
refcount_type.ptr_type(AddressSpace::Generic).into(),
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
// get a pointer to index -1
|
||||
let index_intvalue = refcount_type.const_int((-1 as i64) as u64, false);
|
||||
let refcount_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(ptr_as_usize_ptr, &[index_intvalue], "get_rc_ptr")
|
||||
};
|
||||
|
||||
Self {
|
||||
value: refcount_ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
|
||||
let ptr_as_int = env
|
||||
.builder
|
||||
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
|
||||
.unwrap()
|
||||
.into_int_value();
|
||||
|
||||
let ptr = env.builder.build_int_to_ptr(
|
||||
ptr_as_int,
|
||||
env.context.i64_type().ptr_type(AddressSpace::Generic),
|
||||
"list_int_to_ptr",
|
||||
);
|
||||
|
||||
Self::from_ptr_to_data(env, ptr)
|
||||
}
|
||||
|
||||
pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
|
||||
env.builder
|
||||
.build_load(self.value, "get_refcount")
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
pub fn set_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, refcount: IntValue<'ctx>) {
|
||||
env.builder.build_store(self.value, refcount);
|
||||
}
|
||||
|
||||
fn increment<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) {
|
||||
let refcount = self.get_refcount(env);
|
||||
let builder = env.builder;
|
||||
let refcount_type = ptr_int(env.context, env.ptr_bytes);
|
||||
|
||||
let max = builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
refcount,
|
||||
refcount_type.const_int(REFCOUNT_MAX as u64, false),
|
||||
"refcount_max_check",
|
||||
);
|
||||
let incremented = builder.build_int_add(
|
||||
refcount,
|
||||
refcount_type.const_int(1 as u64, false),
|
||||
"increment_refcount",
|
||||
);
|
||||
|
||||
let new_refcount = builder
|
||||
.build_select(max, refcount, incremented, "select_refcount")
|
||||
.into_int_value();
|
||||
|
||||
self.set_refcount(env, new_refcount);
|
||||
}
|
||||
|
||||
fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) {
|
||||
let context = env.context;
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let alignment = layout.alignment_bytes(env.ptr_bytes);
|
||||
|
||||
let fn_name = &format!("decrement_refcounted_ptr_{}", alignment);
|
||||
|
||||
let function = match env.module.get_function(fn_name) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
// inc and dec return void
|
||||
let fn_type = context.void_type().fn_type(
|
||||
&[context.i64_type().ptr_type(AddressSpace::Generic).into()],
|
||||
false,
|
||||
);
|
||||
|
||||
let function_value =
|
||||
env.module
|
||||
.add_function(fn_name, fn_type, Some(Linkage::Private));
|
||||
|
||||
// Because it's an internal-only function, it should use the fast calling convention.
|
||||
function_value.set_call_conventions(FAST_CALL_CONV);
|
||||
|
||||
let subprogram = env.new_subprogram(fn_name);
|
||||
function_value.set_subprogram(subprogram);
|
||||
|
||||
Self::_build_decrement_function_body(env, function_value, alignment);
|
||||
|
||||
function_value
|
||||
}
|
||||
};
|
||||
|
||||
let refcount_ptr = self.value;
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[refcount_ptr.into()], fn_name);
|
||||
|
||||
call.set_call_convention(FAST_CALL_CONV);
|
||||
}
|
||||
|
||||
fn _build_decrement_function_body<'a, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
extra_bytes: u32,
|
||||
) {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let refcount_type = ptr_int(ctx, env.ptr_bytes);
|
||||
|
||||
let entry = ctx.append_basic_block(parent, "entry");
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let subprogram = parent.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ subprogram.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
&ctx,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
|
||||
env.builder.set_current_debug_location(&ctx, loc);
|
||||
|
||||
let refcount_ptr = {
|
||||
let raw_refcount_ptr = parent.get_nth_param(0).unwrap();
|
||||
debug_assert!(raw_refcount_ptr.is_pointer_value());
|
||||
Self {
|
||||
value: raw_refcount_ptr.into_pointer_value(),
|
||||
}
|
||||
};
|
||||
|
||||
let refcount = refcount_ptr.get_refcount(env);
|
||||
|
||||
let add_with_overflow = env
|
||||
.call_intrinsic(
|
||||
LLVM_SADD_WITH_OVERFLOW_I64,
|
||||
&[
|
||||
refcount.into(),
|
||||
refcount_type.const_int((-1 as i64) as u64, true).into(),
|
||||
],
|
||||
)
|
||||
.into_struct_value();
|
||||
|
||||
let has_overflowed = builder
|
||||
.build_extract_value(add_with_overflow, 1, "has_overflowed")
|
||||
.unwrap();
|
||||
|
||||
let has_overflowed_comparison = builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
has_overflowed.into_int_value(),
|
||||
ctx.bool_type().const_int(1 as u64, false),
|
||||
"has_overflowed",
|
||||
);
|
||||
|
||||
// build blocks
|
||||
let then_block = ctx.append_basic_block(parent, "then");
|
||||
let else_block = ctx.append_basic_block(parent, "else");
|
||||
|
||||
// TODO what would be most optimial for the branch predictor
|
||||
//
|
||||
// are most refcounts 1 most of the time? or not?
|
||||
builder.build_conditional_branch(has_overflowed_comparison, then_block, else_block);
|
||||
|
||||
// build then block
|
||||
{
|
||||
builder.position_at_end(then_block);
|
||||
if !env.leak {
|
||||
match extra_bytes {
|
||||
n if env.ptr_bytes == n => {
|
||||
// the refcount ptr is also the ptr to the malloced region
|
||||
builder.build_free(refcount_ptr.value);
|
||||
}
|
||||
n if 2 * env.ptr_bytes == n => {
|
||||
// we need to step back another ptr_bytes to get the malloced ptr
|
||||
let malloced = Self::from_ptr_to_data(env, refcount_ptr.value);
|
||||
builder.build_free(malloced.value);
|
||||
}
|
||||
n => unreachable!("invalid extra_bytes {:?}", n),
|
||||
}
|
||||
}
|
||||
builder.build_return(None);
|
||||
}
|
||||
|
||||
// build else block
|
||||
{
|
||||
builder.position_at_end(else_block);
|
||||
|
||||
let max = builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
refcount,
|
||||
refcount_type.const_int(REFCOUNT_MAX as u64, false),
|
||||
"refcount_max_check",
|
||||
);
|
||||
let decremented = builder
|
||||
.build_extract_value(add_with_overflow, 0, "decrement_refcount")
|
||||
.unwrap()
|
||||
.into_int_value();
|
||||
let selected = builder.build_select(max, refcount, decremented, "select_refcount");
|
||||
|
||||
refcount_ptr.set_refcount(env, selected.into_int_value());
|
||||
|
||||
builder.build_return(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrement_refcount_struct<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
@ -254,6 +513,7 @@ pub fn build_inc_list<'a, 'ctx, 'env>(
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
) {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let symbol = Symbol::INC;
|
||||
let fn_name = layout_ids
|
||||
@ -263,7 +523,7 @@ pub fn build_inc_list<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let function_value = build_header(env, &layout, fn_name);
|
||||
let function_value = build_header(env, &layout, &fn_name);
|
||||
|
||||
build_inc_list_help(env, layout_ids, layout, function_value);
|
||||
|
||||
@ -272,6 +532,8 @@ pub fn build_inc_list<'a, 'ctx, 'env>(
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[original_wrapper.into()], "increment_list");
|
||||
@ -293,6 +555,23 @@ fn build_inc_list_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = fn_val.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
ctx,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(&ctx, loc);
|
||||
|
||||
let mut scope = Scope::default();
|
||||
|
||||
// Add args to scope
|
||||
@ -332,8 +611,8 @@ fn build_inc_list_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(increment_block);
|
||||
|
||||
let refcount_ptr = list_get_refcount_ptr(env, layout, original_wrapper);
|
||||
increment_refcount_help(env, refcount_ptr);
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
|
||||
refcount_ptr.increment(env);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
@ -350,6 +629,7 @@ pub fn build_dec_list<'a, 'ctx, 'env>(
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
) {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let symbol = Symbol::DEC;
|
||||
let fn_name = layout_ids
|
||||
@ -359,7 +639,7 @@ pub fn build_dec_list<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let function_value = build_header(env, &layout, fn_name);
|
||||
let function_value = build_header(env, &layout, &fn_name);
|
||||
|
||||
build_dec_list_help(env, layout_ids, layout, function_value);
|
||||
|
||||
@ -368,6 +648,8 @@ pub fn build_dec_list<'a, 'ctx, 'env>(
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[original_wrapper.into()], "decrement_list");
|
||||
@ -388,6 +670,23 @@ fn build_dec_list_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = fn_val.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
ctx,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(&ctx, loc);
|
||||
|
||||
let mut scope = Scope::default();
|
||||
|
||||
// Add args to scope
|
||||
@ -410,7 +709,7 @@ fn build_dec_list_help<'a, 'ctx, 'env>(
|
||||
let parent = fn_val;
|
||||
|
||||
// the block we'll always jump to when we're done
|
||||
let cont_block = ctx.append_basic_block(parent, "after_decrement_block");
|
||||
let cont_block = ctx.append_basic_block(parent, "after_decrement_block_build_dec_list_help");
|
||||
let decrement_block = ctx.append_basic_block(parent, "decrement_block");
|
||||
|
||||
// currently, an empty list has a null-pointer in its length is 0
|
||||
@ -431,9 +730,12 @@ fn build_dec_list_help<'a, 'ctx, 'env>(
|
||||
builder.build_conditional_branch(is_non_empty, decrement_block, cont_block);
|
||||
builder.position_at_end(decrement_block);
|
||||
|
||||
let refcount_ptr = list_get_refcount_ptr(env, layout, original_wrapper);
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
|
||||
refcount_ptr.decrement(env, layout);
|
||||
|
||||
decrement_refcount_help(env, parent, refcount_ptr, cont_block);
|
||||
env.builder.build_unconditional_branch(cont_block);
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
// this function returns void
|
||||
builder.build_return(None);
|
||||
@ -446,6 +748,7 @@ pub fn build_inc_str<'a, 'ctx, 'env>(
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
) {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let symbol = Symbol::INC;
|
||||
let fn_name = layout_ids
|
||||
@ -455,7 +758,7 @@ pub fn build_inc_str<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let function_value = build_header(env, &layout, fn_name);
|
||||
let function_value = build_header(env, &layout, &fn_name);
|
||||
|
||||
build_inc_str_help(env, layout_ids, layout, function_value);
|
||||
|
||||
@ -464,6 +767,8 @@ pub fn build_inc_str<'a, 'ctx, 'env>(
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[original_wrapper.into()], "increment_str");
|
||||
@ -484,6 +789,23 @@ fn build_inc_str_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = fn_val.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
ctx,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(&ctx, loc);
|
||||
|
||||
let mut scope = Scope::default();
|
||||
|
||||
// Add args to scope
|
||||
@ -527,8 +849,9 @@ fn build_inc_str_help<'a, 'ctx, 'env>(
|
||||
builder.build_conditional_branch(is_big_and_non_empty, decrement_block, cont_block);
|
||||
builder.position_at_end(decrement_block);
|
||||
|
||||
let refcount_ptr = list_get_refcount_ptr(env, layout, str_wrapper);
|
||||
increment_refcount_help(env, refcount_ptr);
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, str_wrapper);
|
||||
refcount_ptr.increment(env);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
@ -544,6 +867,7 @@ pub fn build_dec_str<'a, 'ctx, 'env>(
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
) {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let symbol = Symbol::DEC;
|
||||
let fn_name = layout_ids
|
||||
@ -553,7 +877,7 @@ pub fn build_dec_str<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let function_value = build_header(env, &layout, fn_name);
|
||||
let function_value = build_header(env, &layout, &fn_name);
|
||||
|
||||
build_dec_str_help(env, layout_ids, layout, function_value);
|
||||
|
||||
@ -562,6 +886,8 @@ pub fn build_dec_str<'a, 'ctx, 'env>(
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[original_wrapper.into()], "decrement_str");
|
||||
@ -582,6 +908,23 @@ fn build_dec_str_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = fn_val.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
ctx,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(&ctx, loc);
|
||||
|
||||
let mut scope = Scope::default();
|
||||
|
||||
// Add args to scope
|
||||
@ -619,155 +962,28 @@ fn build_dec_str_help<'a, 'ctx, 'env>(
|
||||
);
|
||||
|
||||
// the block we'll always jump to when we're done
|
||||
let cont_block = ctx.append_basic_block(parent, "after_decrement_block");
|
||||
let cont_block = ctx.append_basic_block(parent, "after_decrement_block_build_dec_str_help");
|
||||
let decrement_block = ctx.append_basic_block(parent, "decrement_block");
|
||||
|
||||
builder.build_conditional_branch(is_big_and_non_empty, decrement_block, cont_block);
|
||||
builder.position_at_end(decrement_block);
|
||||
|
||||
let refcount_ptr = list_get_refcount_ptr(env, layout, str_wrapper);
|
||||
decrement_refcount_help(env, parent, refcount_ptr, cont_block);
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, str_wrapper);
|
||||
refcount_ptr.decrement(env, layout);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
// this function returns void
|
||||
builder.build_return(None);
|
||||
}
|
||||
|
||||
fn increment_refcount_ptr<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'a>,
|
||||
field_ptr: PointerValue<'ctx>,
|
||||
) {
|
||||
let refcount_ptr = get_refcount_ptr(env, layout, field_ptr);
|
||||
increment_refcount_help(env, refcount_ptr);
|
||||
}
|
||||
|
||||
fn increment_refcount_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
refcount_ptr: PointerValue<'ctx>,
|
||||
) {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let refcount_type = ptr_int(ctx, env.ptr_bytes);
|
||||
|
||||
let refcount = env
|
||||
.builder
|
||||
.build_load(refcount_ptr, "get_refcount")
|
||||
.into_int_value();
|
||||
|
||||
let max = builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
refcount,
|
||||
refcount_type.const_int(REFCOUNT_MAX as u64, false),
|
||||
"refcount_max_check",
|
||||
);
|
||||
let incremented = builder.build_int_add(
|
||||
refcount,
|
||||
refcount_type.const_int(1 as u64, false),
|
||||
"increment_refcount",
|
||||
);
|
||||
let selected = builder.build_select(max, refcount, incremented, "select_refcount");
|
||||
|
||||
builder.build_store(refcount_ptr, selected);
|
||||
}
|
||||
|
||||
fn decrement_refcount_ptr<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
layout: &Layout<'a>,
|
||||
field_ptr: PointerValue<'ctx>,
|
||||
) {
|
||||
let ctx = env.context;
|
||||
|
||||
// the block we'll always jump to when we're done
|
||||
let cont_block = ctx.append_basic_block(parent, "after_decrement_block");
|
||||
|
||||
let refcount_ptr = get_refcount_ptr(env, layout, field_ptr);
|
||||
decrement_refcount_help(env, parent, refcount_ptr, cont_block);
|
||||
}
|
||||
|
||||
fn decrement_refcount_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
refcount_ptr: PointerValue<'ctx>,
|
||||
cont_block: BasicBlock,
|
||||
) {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let refcount_type = ptr_int(ctx, env.ptr_bytes);
|
||||
|
||||
let refcount = env
|
||||
.builder
|
||||
.build_load(refcount_ptr, "get_refcount")
|
||||
.into_int_value();
|
||||
|
||||
let add_with_overflow = env
|
||||
.call_intrinsic(
|
||||
LLVM_SADD_WITH_OVERFLOW_I64,
|
||||
&[
|
||||
refcount.into(),
|
||||
refcount_type.const_int((-1 as i64) as u64, true).into(),
|
||||
],
|
||||
)
|
||||
.into_struct_value();
|
||||
|
||||
let has_overflowed = builder
|
||||
.build_extract_value(add_with_overflow, 1, "has_overflowed")
|
||||
.unwrap();
|
||||
|
||||
let has_overflowed_comparison = builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
has_overflowed.into_int_value(),
|
||||
ctx.bool_type().const_int(1 as u64, false),
|
||||
"has_overflowed",
|
||||
);
|
||||
|
||||
// build blocks
|
||||
let then_block = ctx.append_basic_block(parent, "then");
|
||||
let else_block = ctx.append_basic_block(parent, "else");
|
||||
|
||||
// TODO what would be most optimial for the branch predictor
|
||||
//
|
||||
// are most refcounts 1 most of the time? or not?
|
||||
builder.build_conditional_branch(has_overflowed_comparison, then_block, else_block);
|
||||
|
||||
// build then block
|
||||
{
|
||||
builder.position_at_end(then_block);
|
||||
if !env.leak {
|
||||
builder.build_free(refcount_ptr);
|
||||
}
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
|
||||
// build else block
|
||||
{
|
||||
builder.position_at_end(else_block);
|
||||
|
||||
let max = builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
refcount,
|
||||
refcount_type.const_int(REFCOUNT_MAX as u64, false),
|
||||
"refcount_max_check",
|
||||
);
|
||||
let decremented = builder
|
||||
.build_extract_value(add_with_overflow, 0, "decrement_refcount")
|
||||
.unwrap()
|
||||
.into_int_value();
|
||||
let selected = builder.build_select(max, refcount, decremented, "select_refcount");
|
||||
builder.build_store(refcount_ptr, selected);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
|
||||
// emit merge block
|
||||
builder.position_at_end(cont_block);
|
||||
}
|
||||
|
||||
/// Build an increment or decrement function for a specific layout
|
||||
pub fn build_header<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'a>,
|
||||
fn_name: String,
|
||||
fn_name: &str,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let arena = env.arena;
|
||||
let context = &env.context;
|
||||
@ -779,11 +995,16 @@ pub fn build_header<'a, 'ctx, 'env>(
|
||||
|
||||
let fn_val = env
|
||||
.module
|
||||
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));
|
||||
.add_function(fn_name, fn_type, Some(Linkage::Private));
|
||||
|
||||
// Because it's an internal-only function, it should use the fast calling convention.
|
||||
fn_val.set_call_conventions(FAST_CALL_CONV);
|
||||
|
||||
let subprogram = env.new_subprogram(&fn_name);
|
||||
fn_val.set_subprogram(subprogram);
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
fn_val
|
||||
}
|
||||
|
||||
@ -796,6 +1017,7 @@ pub fn build_dec_union<'a, 'ctx, 'env>(
|
||||
let layout = Layout::Union(fields);
|
||||
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let symbol = Symbol::DEC;
|
||||
let fn_name = layout_ids
|
||||
@ -805,7 +1027,7 @@ pub fn build_dec_union<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let function_value = build_header(env, &layout, fn_name);
|
||||
let function_value = build_header(env, &layout, &fn_name);
|
||||
|
||||
build_dec_union_help(env, layout_ids, fields, function_value);
|
||||
|
||||
@ -814,6 +1036,9 @@ pub fn build_dec_union<'a, 'ctx, 'env>(
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[value], "decrement_union");
|
||||
@ -839,11 +1064,29 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = fn_val.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
context,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(&context, loc);
|
||||
|
||||
let mut scope = Scope::default();
|
||||
|
||||
// Add args to scope
|
||||
let arg_symbol = Symbol::ARG_1;
|
||||
let layout = Layout::Union(tags);
|
||||
|
||||
let arg_val = fn_val.get_param_iter().next().unwrap();
|
||||
|
||||
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
||||
@ -871,6 +1114,8 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
|
||||
|
||||
let merge_block = env.context.append_basic_block(parent, "decrement_merge");
|
||||
|
||||
builder.set_current_debug_location(&context, loc);
|
||||
|
||||
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||
// if none of the fields are or contain anything refcounted, just move on
|
||||
if !field_layouts
|
||||
@ -926,7 +1171,8 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
|
||||
|
||||
// TODO do this decrement before the recursive call?
|
||||
// Then the recursive call is potentially TCE'd
|
||||
decrement_refcount_ptr(env, parent, &layout, recursive_field_ptr);
|
||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, recursive_field_ptr);
|
||||
refcount_ptr.decrement(env, &layout);
|
||||
} else if field_layout.contains_refcounted() {
|
||||
let field_ptr = env
|
||||
.builder
|
||||
@ -983,6 +1229,7 @@ pub fn build_inc_union<'a, 'ctx, 'env>(
|
||||
let layout = Layout::Union(fields);
|
||||
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let symbol = Symbol::INC;
|
||||
let fn_name = layout_ids
|
||||
@ -992,7 +1239,7 @@ pub fn build_inc_union<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let function_value = build_header(env, &layout, fn_name);
|
||||
let function_value = build_header(env, &layout, &fn_name);
|
||||
|
||||
build_inc_union_help(env, layout_ids, fields, function_value);
|
||||
|
||||
@ -1001,6 +1248,8 @@ pub fn build_inc_union<'a, 'ctx, 'env>(
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[value], "increment_union");
|
||||
@ -1026,6 +1275,23 @@ pub fn build_inc_union_help<'a, 'ctx, 'env>(
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let func_scope = fn_val.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
context,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(&context, loc);
|
||||
|
||||
let mut scope = Scope::default();
|
||||
|
||||
// Add args to scope
|
||||
@ -1130,7 +1396,8 @@ pub fn build_inc_union_help<'a, 'ctx, 'env>(
|
||||
|
||||
// TODO do this decrement before the recursive call?
|
||||
// Then the recursive call is potentially TCE'd
|
||||
increment_refcount_ptr(env, &layout, recursive_field_ptr);
|
||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, recursive_field_ptr);
|
||||
refcount_ptr.increment(env);
|
||||
} else if field_layout.contains_refcounted() {
|
||||
let field_ptr = env
|
||||
.builder
|
||||
@ -1184,25 +1451,15 @@ pub fn list_get_refcount_ptr<'a, 'ctx, 'env>(
|
||||
get_refcount_ptr_help(env, layout, ptr_as_int)
|
||||
}
|
||||
|
||||
fn get_refcount_ptr<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'a>,
|
||||
ptr: PointerValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let refcount_type = ptr_int(env.context, env.ptr_bytes);
|
||||
let ptr_as_int =
|
||||
cast_basic_basic(env.builder, ptr.into(), refcount_type.into()).into_int_value();
|
||||
|
||||
get_refcount_ptr_help(env, layout, ptr_as_int)
|
||||
}
|
||||
|
||||
pub fn refcount_offset<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> u64 {
|
||||
let value_bytes = layout.stack_size(env.ptr_bytes) as u64;
|
||||
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::List(_, _)) => env.ptr_bytes as u64,
|
||||
Layout::Builtin(Builtin::Str) => env.ptr_bytes as u64,
|
||||
Layout::RecursivePointer | Layout::RecursiveUnion(_) => env.ptr_bytes as u64,
|
||||
Layout::RecursivePointer | Layout::Union(_) | Layout::RecursiveUnion(_) => {
|
||||
env.ptr_bytes as u64
|
||||
}
|
||||
_ => (env.ptr_bytes as u64).max(value_bytes),
|
||||
}
|
||||
}
|
||||
|
@ -1342,7 +1342,14 @@ mod gen_primitives {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rbtree_balance_2() {
|
||||
#[ignore]
|
||||
fn rbtree_balance_mono_problem() {
|
||||
// because of how the function is written, only `Red` is used and so in the function's
|
||||
// type, the first argument is a unit and dropped. Apparently something is weird with
|
||||
// constraint generation where the specialization required by `main` does not fix the
|
||||
// problem. As a result, the first argument is dropped and we run into issues down the line
|
||||
//
|
||||
// concretely, the `rRight` symbol will not be defined
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -1350,38 +1357,39 @@ mod gen_primitives {
|
||||
|
||||
NodeColor : [ Red, Black ]
|
||||
|
||||
Dict k : [ Node NodeColor k (Dict k), Empty ]
|
||||
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
|
||||
|
||||
balance : NodeColor, k, Dict k, Dict k -> Dict k
|
||||
balance = \color, key, left, right ->
|
||||
# balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
|
||||
balance = \color, key, value, left, right ->
|
||||
when right is
|
||||
Node Red rK _ ->
|
||||
Node Red rK rV rLeft rRight ->
|
||||
when left is
|
||||
Node Red _ _ ->
|
||||
Node Red lK lV lLeft lRight ->
|
||||
Node
|
||||
Red
|
||||
key
|
||||
Empty
|
||||
value
|
||||
(Node Black lK lV lLeft lRight)
|
||||
(Node Black rK rV rLeft rRight)
|
||||
|
||||
_ ->
|
||||
Node color rK (Node Red key left )
|
||||
Node color rK rV (Node Red key value left rLeft) rRight
|
||||
|
||||
_ ->
|
||||
Empty
|
||||
|
||||
main : Dict Int
|
||||
main : Dict Int Int
|
||||
main =
|
||||
balance Red 0 Empty Empty
|
||||
balance Red 0 0 Empty Empty
|
||||
"#
|
||||
),
|
||||
0,
|
||||
1,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn rbtree_balance() {
|
||||
fn rbtree_balance_full() {
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -1431,7 +1439,59 @@ mod gen_primitives {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn nested_pattern_match_two_ways() {
|
||||
// exposed an issue in the ordering of pattern match checks when ran with `--release` mode
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
balance : ConsList Int -> Int
|
||||
balance = \right ->
|
||||
when right is
|
||||
Cons 1 foo ->
|
||||
when foo is
|
||||
Cons 1 _ -> 3
|
||||
_ -> 3
|
||||
_ -> 3
|
||||
|
||||
main : Int
|
||||
main =
|
||||
when balance Nil is
|
||||
_ -> 3
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_non_opt_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
balance : ConsList Int -> Int
|
||||
balance = \right ->
|
||||
when right is
|
||||
Cons 1 (Cons 1 _) -> 3
|
||||
_ -> 3
|
||||
|
||||
main : Int
|
||||
main =
|
||||
when balance Nil is
|
||||
_ -> 3
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linked_list_guarded_double_pattern_match() {
|
||||
// the important part here is that the first case (with the nested Cons) does not match
|
||||
// TODO this also has undefined behavior
|
||||
@ -1445,7 +1505,10 @@ mod gen_primitives {
|
||||
balance : ConsList Int -> Int
|
||||
balance = \right ->
|
||||
when right is
|
||||
Cons 1 (Cons 1 _) -> 3
|
||||
Cons 1 foo ->
|
||||
when foo is
|
||||
Cons 1 _ -> 3
|
||||
_ -> 3
|
||||
_ -> 3
|
||||
|
||||
main : Int
|
||||
|
@ -152,10 +152,14 @@ pub fn helper<'a>(
|
||||
let (module_pass, function_pass) =
|
||||
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
||||
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let env = roc_gen::llvm::build::Env {
|
||||
arena: &arena,
|
||||
builder: &builder,
|
||||
dibuilder: &dibuilder,
|
||||
compile_unit: &compile_unit,
|
||||
context,
|
||||
interns,
|
||||
module,
|
||||
@ -195,6 +199,9 @@ pub fn helper<'a>(
|
||||
|
||||
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
|
||||
|
||||
// call finalize() before any code generation/verification
|
||||
env.dibuilder.finalize();
|
||||
|
||||
if fn_val.verify(true) {
|
||||
function_pass.run_on(&fn_val);
|
||||
} else {
|
||||
@ -228,6 +235,8 @@ pub fn helper<'a>(
|
||||
&main_fn_layout,
|
||||
);
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
|
@ -1316,7 +1316,7 @@ fn compile_tests<'a>(
|
||||
cond = compile_guard(env, ret_layout.clone(), id, arena.alloc(stmt), fail, cond);
|
||||
}
|
||||
|
||||
for (new_stores, lhs, rhs, _layout) in tests.into_iter().rev() {
|
||||
for (new_stores, lhs, rhs, _layout) in tests.into_iter() {
|
||||
cond = compile_test(env, ret_layout.clone(), new_stores, lhs, rhs, fail, cond);
|
||||
}
|
||||
cond
|
||||
|
@ -5332,6 +5332,10 @@ pub fn from_can_pattern<'a>(
|
||||
|
||||
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
// disregard the tag discriminant layout
|
||||
|
||||
// TODO make this assert pass, it currently does not because
|
||||
// 0-sized values are dropped out
|
||||
// debug_assert_eq!(arguments.len(), argument_layouts[1..].len());
|
||||
let it = argument_layouts[1..].iter();
|
||||
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
|
||||
mono_args.push((
|
||||
|
@ -432,9 +432,35 @@ impl<'a> Layout<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alignment_bytes(&self, pointer_size: u32) -> u32 {
|
||||
match self {
|
||||
Layout::Struct(fields) => fields
|
||||
.iter()
|
||||
.map(|x| x.alignment_bytes(pointer_size))
|
||||
.max()
|
||||
.unwrap_or(0),
|
||||
Layout::Union(tags) | Layout::RecursiveUnion(tags) => tags
|
||||
.iter()
|
||||
.map(|x| x.iter())
|
||||
.flatten()
|
||||
.map(|x| x.alignment_bytes(pointer_size))
|
||||
.max()
|
||||
.unwrap_or(0),
|
||||
Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size),
|
||||
Layout::PhantomEmptyStruct => 0,
|
||||
Layout::RecursivePointer => pointer_size,
|
||||
Layout::FunctionPointer(_, _) => pointer_size,
|
||||
Layout::Pointer(_) => pointer_size,
|
||||
Layout::Closure(_, captured, _) => {
|
||||
pointer_size.max(captured.layout.alignment_bytes(pointer_size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_refcounted(&self) -> bool {
|
||||
match self {
|
||||
Layout::Builtin(Builtin::List(_, _)) => true,
|
||||
Layout::Builtin(Builtin::Str) => true,
|
||||
Layout::RecursiveUnion(_) => true,
|
||||
Layout::RecursivePointer => true,
|
||||
_ => false,
|
||||
@ -632,6 +658,31 @@ impl<'a> Builtin<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alignment_bytes(&self, pointer_size: u32) -> u32 {
|
||||
use std::mem::align_of;
|
||||
use Builtin::*;
|
||||
|
||||
// for our data structures, what counts is the alignment of the `( ptr, len )` tuple, and
|
||||
// since both of those are one pointer size, the alignment of that structure is a pointer
|
||||
// size
|
||||
match self {
|
||||
Int128 => align_of::<i128>() as u32,
|
||||
Int64 => align_of::<i64>() as u32,
|
||||
Int32 => align_of::<i32>() as u32,
|
||||
Int16 => align_of::<i16>() as u32,
|
||||
Int8 => align_of::<i8>() as u32,
|
||||
Int1 => align_of::<bool>() as u32,
|
||||
Float128 => align_of::<i128>() as u32,
|
||||
Float64 => align_of::<f64>() as u32,
|
||||
Float32 => align_of::<f32>() as u32,
|
||||
Float16 => align_of::<i16>() as u32,
|
||||
Str | EmptyStr => pointer_size,
|
||||
Map(_, _) | EmptyMap => pointer_size,
|
||||
Set(_) | EmptySet => pointer_size,
|
||||
List(_, _) | EmptyList => pointer_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn safe_to_memcpy(&self) -> bool {
|
||||
use Builtin::*;
|
||||
|
||||
|
@ -524,14 +524,14 @@ mod test_mono {
|
||||
let Test.8 = 1i64;
|
||||
ret Test.8;
|
||||
in
|
||||
let Test.9 = Index 1 Test.2;
|
||||
let Test.10 = 0i64;
|
||||
let Test.11 = Index 0 Test.9;
|
||||
let Test.16 = lowlevel Eq Test.10 Test.11;
|
||||
let Test.12 = 0i64;
|
||||
let Test.13 = Index 0 Test.2;
|
||||
let Test.16 = lowlevel Eq Test.12 Test.13;
|
||||
if Test.16 then
|
||||
let Test.12 = 0i64;
|
||||
let Test.13 = Index 0 Test.2;
|
||||
let Test.15 = lowlevel Eq Test.12 Test.13;
|
||||
let Test.9 = Index 1 Test.2;
|
||||
let Test.10 = 0i64;
|
||||
let Test.11 = Index 0 Test.9;
|
||||
let Test.15 = lowlevel Eq Test.10 Test.11;
|
||||
if Test.15 then
|
||||
let Test.7 = Index 1 Test.2;
|
||||
let Test.3 = Index 1 Test.7;
|
||||
@ -571,13 +571,13 @@ mod test_mono {
|
||||
let Test.5 = CallByName Num.14 Test.1 Test.2;
|
||||
ret Test.5;
|
||||
in
|
||||
let Test.7 = Index 0 Test.3;
|
||||
let Test.8 = 4i64;
|
||||
let Test.13 = lowlevel Eq Test.8 Test.7;
|
||||
let Test.9 = Index 1 Test.3;
|
||||
let Test.10 = 3i64;
|
||||
let Test.13 = lowlevel Eq Test.10 Test.9;
|
||||
if Test.13 then
|
||||
let Test.9 = Index 1 Test.3;
|
||||
let Test.10 = 3i64;
|
||||
let Test.12 = lowlevel Eq Test.10 Test.9;
|
||||
let Test.7 = Index 0 Test.3;
|
||||
let Test.8 = 4i64;
|
||||
let Test.12 = lowlevel Eq Test.8 Test.7;
|
||||
if Test.12 then
|
||||
let Test.4 = 9i64;
|
||||
ret Test.4;
|
||||
@ -1328,15 +1328,15 @@ mod test_mono {
|
||||
let Test.18 = Array [];
|
||||
ret Test.18;
|
||||
in
|
||||
let Test.19 = Index 0 Test.7;
|
||||
let Test.20 = 1i64;
|
||||
let Test.21 = Index 0 Test.19;
|
||||
let Test.27 = lowlevel Eq Test.20 Test.21;
|
||||
let Test.22 = Index 1 Test.7;
|
||||
let Test.23 = 1i64;
|
||||
let Test.24 = Index 0 Test.22;
|
||||
let Test.27 = lowlevel Eq Test.23 Test.24;
|
||||
if Test.27 then
|
||||
let Test.22 = Index 1 Test.7;
|
||||
let Test.23 = 1i64;
|
||||
let Test.24 = Index 0 Test.22;
|
||||
let Test.26 = lowlevel Eq Test.23 Test.24;
|
||||
let Test.19 = Index 0 Test.7;
|
||||
let Test.20 = 1i64;
|
||||
let Test.21 = Index 0 Test.19;
|
||||
let Test.26 = lowlevel Eq Test.20 Test.21;
|
||||
if Test.26 then
|
||||
let Test.17 = Index 0 Test.7;
|
||||
let Test.3 = Index 1 Test.17;
|
||||
@ -2019,14 +2019,14 @@ mod test_mono {
|
||||
let Test.8 = 1i64;
|
||||
ret Test.8;
|
||||
in
|
||||
let Test.9 = Index 1 Test.2;
|
||||
let Test.10 = 0i64;
|
||||
let Test.11 = Index 0 Test.9;
|
||||
let Test.16 = lowlevel Eq Test.10 Test.11;
|
||||
let Test.12 = 0i64;
|
||||
let Test.13 = Index 0 Test.2;
|
||||
let Test.16 = lowlevel Eq Test.12 Test.13;
|
||||
if Test.16 then
|
||||
let Test.12 = 0i64;
|
||||
let Test.13 = Index 0 Test.2;
|
||||
let Test.15 = lowlevel Eq Test.12 Test.13;
|
||||
let Test.9 = Index 1 Test.2;
|
||||
let Test.10 = 0i64;
|
||||
let Test.11 = Index 0 Test.9;
|
||||
let Test.15 = lowlevel Eq Test.10 Test.11;
|
||||
if Test.15 then
|
||||
let Test.7 = Index 1 Test.2;
|
||||
let Test.3 = Index 1 Test.7;
|
||||
@ -2155,15 +2155,15 @@ mod test_mono {
|
||||
let Test.21 = Array [];
|
||||
ret Test.21;
|
||||
in
|
||||
let Test.22 = Index 0 Test.12;
|
||||
let Test.23 = 1i64;
|
||||
let Test.24 = Index 0 Test.22;
|
||||
let Test.30 = lowlevel Eq Test.23 Test.24;
|
||||
let Test.25 = Index 1 Test.12;
|
||||
let Test.26 = 1i64;
|
||||
let Test.27 = Index 0 Test.25;
|
||||
let Test.30 = lowlevel Eq Test.26 Test.27;
|
||||
if Test.30 then
|
||||
let Test.25 = Index 1 Test.12;
|
||||
let Test.26 = 1i64;
|
||||
let Test.27 = Index 0 Test.25;
|
||||
let Test.29 = lowlevel Eq Test.26 Test.27;
|
||||
let Test.22 = Index 0 Test.12;
|
||||
let Test.23 = 1i64;
|
||||
let Test.24 = Index 0 Test.22;
|
||||
let Test.29 = lowlevel Eq Test.23 Test.24;
|
||||
if Test.29 then
|
||||
let Test.20 = Index 0 Test.12;
|
||||
let Test.5 = Index 1 Test.20;
|
||||
|
@ -437,7 +437,7 @@ pub enum Pattern<'a> {
|
||||
},
|
||||
FloatLiteral(&'a str),
|
||||
StrLiteral(StrLiteral<'a>),
|
||||
Underscore,
|
||||
Underscore(&'a str),
|
||||
|
||||
// Space
|
||||
SpaceBefore(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]),
|
||||
@ -554,7 +554,7 @@ impl<'a> Pattern<'a> {
|
||||
) => string_x == string_y && base_x == base_y && is_negative_x == is_negative_y,
|
||||
(FloatLiteral(x), FloatLiteral(y)) => x == y,
|
||||
(StrLiteral(x), StrLiteral(y)) => x == y,
|
||||
(Underscore, Underscore) => true,
|
||||
(Underscore(x), Underscore(y)) => x == y,
|
||||
|
||||
// Space
|
||||
(SpaceBefore(x, _), SpaceBefore(y, _)) => x.equivalent(y),
|
||||
|
@ -666,7 +666,7 @@ fn annotation_or_alias<'a>(
|
||||
NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => {
|
||||
Def::NotYetImplemented("TODO gracefully handle trying to annotate a litera")
|
||||
}
|
||||
Underscore => {
|
||||
Underscore(_) => {
|
||||
Def::NotYetImplemented("TODO gracefully handle trying to give a type annotation to an undrscore")
|
||||
}
|
||||
Malformed(_) => {
|
||||
@ -1086,7 +1086,16 @@ fn string_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||
}
|
||||
|
||||
fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||
map!(ascii_char(b'_'), |_| Pattern::Underscore)
|
||||
move |arena: &'a Bump, state: State<'a>| {
|
||||
let (_, next_state) = ascii_char(b'_').parse(arena, state)?;
|
||||
|
||||
let (output, final_state) = optional(lowercase_ident()).parse(arena, next_state)?;
|
||||
|
||||
match output {
|
||||
Some(name) => Ok((Pattern::Underscore(name), final_state)),
|
||||
None => Ok((Pattern::Underscore(&""), final_state)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
||||
|
@ -1414,7 +1414,7 @@ mod test_parse {
|
||||
#[test]
|
||||
fn single_underscore_closure() {
|
||||
let arena = Bump::new();
|
||||
let pattern = Located::new(0, 0, 1, 2, Underscore);
|
||||
let pattern = Located::new(0, 0, 1, 2, Underscore(&""));
|
||||
let patterns = &[pattern];
|
||||
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42"))));
|
||||
let actual = parse_expr_with(&arena, "\\_ -> 42");
|
||||
@ -1464,14 +1464,14 @@ mod test_parse {
|
||||
#[test]
|
||||
fn closure_with_underscores() {
|
||||
let arena = Bump::new();
|
||||
let underscore1 = Located::new(0, 0, 1, 2, Underscore);
|
||||
let underscore2 = Located::new(0, 0, 4, 5, Underscore);
|
||||
let underscore1 = Located::new(0, 0, 1, 2, Underscore(&""));
|
||||
let underscore2 = Located::new(0, 0, 4, 9, Underscore(&"name"));
|
||||
let patterns = bumpalo::vec![in &arena; underscore1, underscore2];
|
||||
let expected = Closure(
|
||||
arena.alloc(patterns),
|
||||
arena.alloc(Located::new(0, 0, 9, 11, Num("42"))),
|
||||
arena.alloc(Located::new(0, 0, 13, 15, Num("42"))),
|
||||
);
|
||||
let actual = parse_expr_with(&arena, "\\_, _ -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\_, _name -> 42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
@ -2477,7 +2477,7 @@ mod test_parse {
|
||||
guard: None,
|
||||
});
|
||||
let newlines = &[Newline];
|
||||
let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore), newlines);
|
||||
let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore(&"")), newlines);
|
||||
let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2);
|
||||
let expr2 = Num("4");
|
||||
let loc_expr2 = Located::new(2, 2, 9, 10, expr2);
|
||||
@ -2522,7 +2522,7 @@ mod test_parse {
|
||||
guard: None,
|
||||
});
|
||||
let newlines = &[Newline];
|
||||
let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore), newlines);
|
||||
let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore(&"")), newlines);
|
||||
let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2);
|
||||
let expr2 = Num("4");
|
||||
let loc_expr2 = Located::new(2, 2, 9, 10, expr2);
|
||||
|
@ -3614,4 +3614,33 @@ mod solve_expr {
|
||||
"Dict Int Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn pattern_rigid_problem() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
|
||||
Dict k : [ Node k (Dict k) (Dict k), Empty ]
|
||||
|
||||
balance : k, Dict k -> Dict k
|
||||
balance = \key, left ->
|
||||
when left is
|
||||
Node _ _ lRight ->
|
||||
Node key lRight Empty
|
||||
|
||||
_ ->
|
||||
Empty
|
||||
|
||||
|
||||
main : Dict Int
|
||||
main =
|
||||
balance 0 Empty
|
||||
"#
|
||||
),
|
||||
"Dict Int",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded",
|
||||
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
|
||||
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
|
||||
# This way, GitHub Actions works and nobody's builds get broken.
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
|
||||
target-lexicon = "0.10"
|
||||
winit = "0.22"
|
||||
wgpu = "0.6"
|
||||
|
@ -75,9 +75,12 @@ unsafe fn free_or_noop(ptr: *mut c_void) {
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[inline(always)]
|
||||
fn free_or_noop(_ptr: *mut c_void) {
|
||||
unsafe fn free_or_noop(ptr: *mut c_void) {
|
||||
// In release builds, we'll have used alloca,
|
||||
// so there's nothing to free.
|
||||
|
||||
// except that for now we always use malloc
|
||||
libc::free(ptr)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
Loading…
Reference in New Issue
Block a user