Merge remote-tracking branch 'origin' into dev-backend

This commit is contained in:
Brendan Hansknecht 2020-11-18 23:54:31 -08:00
commit 0cee2cd96f
26 changed files with 1250 additions and 978 deletions

View file

@ -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]

View file

@ -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();

View file

@ -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),
},

View file

@ -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) => {

View file

@ -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"

View file

@ -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);

View file

@ -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
}

View file

@ -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);

View file

@ -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),
}
}

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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((

View file

@ -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::*;

View file

@ -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;

View file

@ -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),

View file

@ -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>> {

View file

@ -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);

View file

@ -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",
);
}
}