mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
Merge remote-tracking branch 'origin' into dev-backend
This commit is contained in:
commit
0cee2cd96f
26 changed files with 1250 additions and 978 deletions
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue