mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Merge remote-tracking branch 'origin/trunk' into exception-handling
This commit is contained in:
commit
16fc5dd497
23 changed files with 1280 additions and 351 deletions
|
@ -1,10 +1,10 @@
|
|||
use crate::layout_id::LayoutIds;
|
||||
use crate::llvm::build_list::{
|
||||
allocate_list, build_basic_phi2, clone_nonempty_list, empty_list, empty_polymorphic_list,
|
||||
incrementing_elem_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty,
|
||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_get_unsafe,
|
||||
list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set,
|
||||
list_single, list_walk_right, load_list_ptr, store_list,
|
||||
list_single, list_walk_right,
|
||||
};
|
||||
use crate::llvm::build_str::{str_concat, str_len, CHAR_LAYOUT};
|
||||
use crate::llvm::compare::{build_eq, build_neq};
|
||||
use crate::llvm::convert::{
|
||||
basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int,
|
||||
|
@ -23,10 +23,12 @@ use inkwell::module::{Linkage, Module};
|
|||
use inkwell::passes::{PassManager, PassManagerBuilder};
|
||||
use inkwell::types::{BasicTypeEnum, FunctionType, IntType, StructType};
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::InstructionOpcode;
|
||||
use inkwell::values::{BasicValue, FloatValue, FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::AddressSpace;
|
||||
use inkwell::values::{
|
||||
BasicValue, CallSiteValue, FloatValue, FunctionValue, InstructionOpcode, IntValue,
|
||||
PointerValue, StructValue,
|
||||
};
|
||||
use inkwell::OptimizationLevel;
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use roc_collections::all::{ImMap, MutSet};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
|
@ -95,6 +97,77 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
|
|||
pub fn ptr_int(&self) -> IntType<'ctx> {
|
||||
ptr_int(self.context, self.ptr_bytes)
|
||||
}
|
||||
|
||||
pub fn small_str_bytes(&self) -> u32 {
|
||||
self.ptr_bytes * 2
|
||||
}
|
||||
|
||||
pub fn build_intrinsic_call(
|
||||
&self,
|
||||
intrinsic_name: &'static str,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
) -> CallSiteValue<'ctx> {
|
||||
let fn_val = self
|
||||
.module
|
||||
.get_function(intrinsic_name)
|
||||
.unwrap_or_else(|| panic!("Unrecognized intrinsic function: {}", intrinsic_name));
|
||||
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), self.arena);
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(*arg);
|
||||
}
|
||||
|
||||
let call = self
|
||||
.builder
|
||||
.build_call(fn_val, arg_vals.into_bump_slice(), "call");
|
||||
|
||||
call.set_call_convention(fn_val.get_call_conventions());
|
||||
|
||||
call
|
||||
}
|
||||
|
||||
pub fn call_intrinsic(
|
||||
&self,
|
||||
intrinsic_name: &'static str,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let call = self.build_intrinsic_call(intrinsic_name, args);
|
||||
|
||||
call.try_as_basic_value().left().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"LLVM error: Invalid call by name for intrinsic {}",
|
||||
intrinsic_name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call_memset(
|
||||
&self,
|
||||
bytes_ptr: PointerValue<'ctx>,
|
||||
filler: IntValue<'ctx>,
|
||||
length: IntValue<'ctx>,
|
||||
) -> CallSiteValue<'ctx> {
|
||||
let false_val = self.context.bool_type().const_int(0, false);
|
||||
|
||||
let intrinsic_name = match self.ptr_bytes {
|
||||
8 => LLVM_MEMSET_I64,
|
||||
4 => LLVM_MEMSET_I32,
|
||||
other => {
|
||||
unreachable!("Unsupported number of ptr_bytes {:?}", other);
|
||||
}
|
||||
};
|
||||
|
||||
self.build_intrinsic_call(
|
||||
intrinsic_name,
|
||||
&[
|
||||
bytes_ptr.into(),
|
||||
filler.into(),
|
||||
length.into(),
|
||||
false_val.into(),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Module<'ctx> {
|
||||
|
@ -114,8 +187,41 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
|||
// List of all supported LLVM intrinsics:
|
||||
//
|
||||
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
|
||||
let i64_type = ctx.i64_type();
|
||||
let void_type = ctx.void_type();
|
||||
let i1_type = ctx.bool_type();
|
||||
let f64_type = ctx.f64_type();
|
||||
let i64_type = ctx.i64_type();
|
||||
let i32_type = ctx.i32_type();
|
||||
let i8_type = ctx.i8_type();
|
||||
let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic);
|
||||
|
||||
add_intrinsic(
|
||||
module,
|
||||
LLVM_MEMSET_I64,
|
||||
void_type.fn_type(
|
||||
&[
|
||||
i8_ptr_type.into(),
|
||||
i8_type.into(),
|
||||
i64_type.into(),
|
||||
i1_type.into(),
|
||||
],
|
||||
false,
|
||||
),
|
||||
);
|
||||
|
||||
add_intrinsic(
|
||||
module,
|
||||
LLVM_MEMSET_I32,
|
||||
void_type.fn_type(
|
||||
&[
|
||||
i8_ptr_type.into(),
|
||||
i8_type.into(),
|
||||
i32_type.into(),
|
||||
i1_type.into(),
|
||||
],
|
||||
false,
|
||||
),
|
||||
);
|
||||
|
||||
add_intrinsic(
|
||||
module,
|
||||
|
@ -158,8 +264,16 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
|||
LLVM_CEILING_F64,
|
||||
f64_type.fn_type(&[f64_type.into()], false),
|
||||
);
|
||||
|
||||
add_intrinsic(
|
||||
module,
|
||||
LLVM_FLOOR_F64,
|
||||
f64_type.fn_type(&[f64_type.into()], false),
|
||||
);
|
||||
}
|
||||
|
||||
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
|
||||
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
|
||||
static LLVM_SQRT_F64: &str = "llvm.sqrt.f64";
|
||||
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
|
||||
static LLVM_FABS_F64: &str = "llvm.fabs.f64";
|
||||
|
@ -167,6 +281,7 @@ static LLVM_SIN_F64: &str = "llvm.sin.f64";
|
|||
static LLVM_COS_F64: &str = "llvm.cos.f64";
|
||||
static LLVM_POW_F64: &str = "llvm.pow.f64";
|
||||
static LLVM_CEILING_F64: &str = "llvm.ceil.f64";
|
||||
static LLVM_FLOOR_F64: &str = "llvm.floor.f64";
|
||||
|
||||
fn add_intrinsic<'ctx>(
|
||||
module: &Module<'ctx>,
|
||||
|
@ -521,70 +636,124 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
|
|||
} else {
|
||||
let ctx = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
let len_u64 = str_literal.len() as u64;
|
||||
|
||||
let elem_bytes = CHAR_LAYOUT.stack_size(env.ptr_bytes) as u64;
|
||||
let ptr_bytes = env.ptr_bytes;
|
||||
|
||||
let ptr = {
|
||||
let populate_str = |ptr| {
|
||||
// Copy the elements from the list literal into the array
|
||||
for (index, char) in str_literal.as_bytes().iter().enumerate() {
|
||||
let val = env
|
||||
.context
|
||||
.i8_type()
|
||||
.const_int(*char as u64, false)
|
||||
.as_basic_value_enum();
|
||||
let index_val = ctx.i64_type().const_int(index as u64, false);
|
||||
let elem_ptr =
|
||||
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
|
||||
|
||||
builder.build_store(elem_ptr, val);
|
||||
}
|
||||
};
|
||||
|
||||
if str_literal.len() < env.small_str_bytes() as usize {
|
||||
// TODO support big endian systems
|
||||
|
||||
let array_alloca = builder.build_array_alloca(
|
||||
ctx.i8_type(),
|
||||
ctx.i8_type().const_int(env.small_str_bytes() as u64, false),
|
||||
"alloca_small_str",
|
||||
);
|
||||
|
||||
// Zero out all the bytes. If we don't do this, then
|
||||
// small strings would have uninitialized bytes, which could
|
||||
// cause string equality checks to fail randomly.
|
||||
//
|
||||
// We're running memset over *all* the bytes, even though
|
||||
// the final one is about to be manually overridden, on
|
||||
// the theory that LLVM will optimize the memset call
|
||||
// into two instructions to move appropriately-sized
|
||||
// zero integers into the appropriate locations instead
|
||||
// of doing any iteration.
|
||||
//
|
||||
// TODO: look at the compiled output to verify this theory!
|
||||
env.call_memset(
|
||||
array_alloca,
|
||||
ctx.i8_type().const_zero(),
|
||||
env.ptr_int().const_int(env.small_str_bytes() as u64, false),
|
||||
);
|
||||
|
||||
let final_byte = (str_literal.len() as u8) | 0b1000_0000;
|
||||
|
||||
let final_byte_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
array_alloca,
|
||||
&[ctx
|
||||
.i8_type()
|
||||
.const_int(env.small_str_bytes() as u64 - 1, false)],
|
||||
"str_literal_final_byte",
|
||||
)
|
||||
};
|
||||
|
||||
builder.build_store(
|
||||
final_byte_ptr,
|
||||
ctx.i8_type().const_int(final_byte as u64, false),
|
||||
);
|
||||
|
||||
populate_str(array_alloca);
|
||||
|
||||
builder.build_load(
|
||||
builder
|
||||
.build_bitcast(
|
||||
array_alloca,
|
||||
collection(ctx, ptr_bytes).ptr_type(AddressSpace::Generic),
|
||||
"cast_collection",
|
||||
)
|
||||
.into_pointer_value(),
|
||||
"small_str_array",
|
||||
)
|
||||
} else {
|
||||
let bytes_len = elem_bytes * len_u64;
|
||||
let len_type = env.ptr_int();
|
||||
let len = len_type.const_int(bytes_len, false);
|
||||
|
||||
allocate_list(env, InPlace::Clone, &CHAR_LAYOUT, len)
|
||||
let ptr = allocate_list(env, InPlace::Clone, &CHAR_LAYOUT, len);
|
||||
let int_type = ptr_int(ctx, ptr_bytes);
|
||||
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
|
||||
let struct_type = collection(ctx, ptr_bytes);
|
||||
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
|
||||
|
||||
// TODO check if malloc returned null; if so, runtime error for OOM!
|
||||
};
|
||||
let mut struct_val;
|
||||
|
||||
// Copy the elements from the list literal into the array
|
||||
for (index, char) in str_literal.as_bytes().iter().enumerate() {
|
||||
let val = env
|
||||
.context
|
||||
.i8_type()
|
||||
.const_int(*char as u64, false)
|
||||
.as_basic_value_enum();
|
||||
let index_val = ctx.i64_type().const_int(index as u64, false);
|
||||
let elem_ptr =
|
||||
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
|
||||
// Store the pointer
|
||||
struct_val = builder
|
||||
.build_insert_value(
|
||||
struct_type.get_undef(),
|
||||
ptr_as_int,
|
||||
Builtin::WRAPPER_PTR,
|
||||
"insert_ptr",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder.build_store(elem_ptr, val);
|
||||
}
|
||||
// Store the length
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
|
||||
.unwrap();
|
||||
|
||||
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, "list_cast_ptr");
|
||||
let struct_type = collection(ctx, ptr_bytes);
|
||||
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
|
||||
let mut struct_val;
|
||||
populate_str(ptr);
|
||||
|
||||
// Store the pointer
|
||||
struct_val = builder
|
||||
.build_insert_value(
|
||||
struct_type.get_undef(),
|
||||
ptr_as_int,
|
||||
Builtin::WRAPPER_PTR,
|
||||
"insert_ptr",
|
||||
builder.build_bitcast(
|
||||
struct_val.into_struct_value(),
|
||||
collection(ctx, ptr_bytes),
|
||||
"cast_collection",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Store the length
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
|
||||
.unwrap();
|
||||
|
||||
// Bitcast to an array of raw bytes
|
||||
builder.build_bitcast(
|
||||
struct_val.into_struct_value(),
|
||||
collection(ctx, ptr_bytes),
|
||||
"cast_collection",
|
||||
)
|
||||
// TODO check if malloc returned null; if so, runtime error for OOM!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
|
||||
|
||||
pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
|
@ -1390,6 +1559,16 @@ pub fn load_symbol<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn ptr_from_symbol<'a, 'ctx, 'scope>(
|
||||
scope: &'scope Scope<'a, 'ctx>,
|
||||
symbol: Symbol,
|
||||
) -> &'scope PointerValue<'ctx> {
|
||||
match scope.get(&symbol) {
|
||||
Some((_, ptr)) => ptr,
|
||||
None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_symbol_and_layout<'a, 'ctx, 'env, 'b>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &'b Scope<'a, 'ctx>,
|
||||
|
@ -1743,36 +1922,6 @@ fn call_with_args<'a, 'ctx, 'env>(
|
|||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by name for name {:?}", symbol))
|
||||
}
|
||||
|
||||
fn call_intrinsic<'a, 'ctx, 'env>(
|
||||
intrinsic_name: &'static str,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let fn_val = env
|
||||
.module
|
||||
.get_function(intrinsic_name)
|
||||
.unwrap_or_else(|| panic!("Unrecognized intrinsic function: {}", intrinsic_name));
|
||||
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for (arg, _layout) in args.iter() {
|
||||
arg_vals.push(*arg);
|
||||
}
|
||||
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(fn_val, arg_vals.into_bump_slice(), "call");
|
||||
|
||||
call.set_call_convention(fn_val.get_call_conventions());
|
||||
|
||||
call.try_as_basic_value().left().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"LLVM error: Invalid call by name for intrinsic {}",
|
||||
intrinsic_name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum InPlace {
|
||||
InPlace,
|
||||
|
@ -1813,13 +1962,23 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
// Str.concat : Str, Str -> Str
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let first_str = load_symbol(env, scope, &args[0]);
|
||||
|
||||
let second_str = load_symbol(env, scope, &args[1]);
|
||||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
|
||||
str_concat(env, inplace, parent, first_str, second_str)
|
||||
str_concat(env, inplace, scope, parent, args[0], args[1])
|
||||
}
|
||||
StrIsEmpty => {
|
||||
// Str.isEmpty : Str -> Str
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let wrapper_ptr = ptr_from_symbol(scope, args[0]);
|
||||
let len = str_len(env, parent, *wrapper_ptr);
|
||||
let is_zero = env.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
len,
|
||||
env.ptr_int().const_zero(),
|
||||
"str_len_is_zero",
|
||||
);
|
||||
BasicValueEnum::IntValue(is_zero)
|
||||
}
|
||||
ListLen => {
|
||||
// List.len : List * -> Int
|
||||
|
@ -1948,7 +2107,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
list_join(env, inplace, parent, list, outer_list_layout)
|
||||
}
|
||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling
|
||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor
|
||||
| NumToFloat => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
|
@ -1963,7 +2122,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
build_int_unary_op(env, arg.into_int_value(), arg_layout, op)
|
||||
}
|
||||
Float128 | Float64 | Float32 | Float16 => {
|
||||
build_float_unary_op(env, arg.into_float_value(), arg_layout, op)
|
||||
build_float_unary_op(env, arg.into_float_value(), op)
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout);
|
||||
|
@ -1980,7 +2139,6 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
NumCompare => {
|
||||
use inkwell::FloatPredicate;
|
||||
use inkwell::IntPredicate;
|
||||
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
|
@ -2061,7 +2219,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
|
||||
| NumDivUnchecked | NumPow => {
|
||||
| NumDivUnchecked | NumPow | NumPowInt => {
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||
|
@ -2253,179 +2411,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Str.concat : Str, Str -> Str
|
||||
fn str_concat<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
parent: FunctionValue<'ctx>,
|
||||
first_str: BasicValueEnum<'ctx>,
|
||||
second_str: BasicValueEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
|
||||
let second_str_wrapper = second_str.into_struct_value();
|
||||
let second_str_len = list_len(builder, second_str_wrapper);
|
||||
|
||||
let first_str_wrapper = first_str.into_struct_value();
|
||||
let first_str_len = list_len(builder, first_str_wrapper);
|
||||
|
||||
// first_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the first input
|
||||
// str is empty, then we can just return the second str cloned
|
||||
let first_str_length_comparison = list_is_not_empty(builder, ctx, first_str_len);
|
||||
|
||||
let if_first_str_is_empty = || {
|
||||
// second_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// str is empty, then we can just return an empty str
|
||||
let second_str_length_comparison = list_is_not_empty(builder, ctx, second_str_len);
|
||||
|
||||
let build_second_str_then = || {
|
||||
let char_type = basic_type_from_layout(env.arena, ctx, &CHAR_LAYOUT, env.ptr_bytes);
|
||||
let ptr_type = get_ptr_type(&char_type, AddressSpace::Generic);
|
||||
|
||||
let (new_wrapper, _) = clone_nonempty_list(
|
||||
env,
|
||||
inplace,
|
||||
second_str_len,
|
||||
load_list_ptr(builder, second_str_wrapper, ptr_type),
|
||||
&CHAR_LAYOUT,
|
||||
);
|
||||
|
||||
BasicValueEnum::StructValue(new_wrapper)
|
||||
};
|
||||
|
||||
let build_second_str_else = || empty_list(env);
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
second_str_length_comparison,
|
||||
build_second_str_then,
|
||||
build_second_str_else,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
};
|
||||
|
||||
let if_first_str_is_not_empty = || {
|
||||
let char_type = ctx.i8_type().into();
|
||||
let ptr_type = get_ptr_type(&char_type, AddressSpace::Generic);
|
||||
|
||||
let if_second_str_is_empty = || {
|
||||
let (new_wrapper, _) = clone_nonempty_list(
|
||||
env,
|
||||
inplace,
|
||||
first_str_len,
|
||||
load_list_ptr(builder, first_str_wrapper, ptr_type),
|
||||
&CHAR_LAYOUT,
|
||||
);
|
||||
|
||||
BasicValueEnum::StructValue(new_wrapper)
|
||||
};
|
||||
|
||||
// second_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// str is empty, then we can just return the first str cloned
|
||||
let second_str_length_comparison = list_is_not_empty(builder, ctx, second_str_len);
|
||||
|
||||
let if_second_str_is_not_empty = || {
|
||||
let combined_str_len =
|
||||
builder.build_int_add(first_str_len, second_str_len, "add_list_lengths");
|
||||
|
||||
let combined_str_ptr = allocate_list(env, inplace, &CHAR_LAYOUT, combined_str_len);
|
||||
|
||||
// FIRST LOOP
|
||||
let first_str_ptr = load_list_ptr(builder, first_str_wrapper, ptr_type);
|
||||
|
||||
let first_loop = |first_index, first_str_elem| {
|
||||
// The pointer to the element in the combined list
|
||||
let combined_str_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_elem_ptr, first_str_elem);
|
||||
};
|
||||
|
||||
let index_name = "#index";
|
||||
|
||||
let index_alloca = incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
first_str_ptr,
|
||||
first_str_len,
|
||||
index_name,
|
||||
first_loop,
|
||||
);
|
||||
|
||||
// Reset the index variable to 0
|
||||
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||
|
||||
// SECOND LOOP
|
||||
let second_str_ptr = load_list_ptr(builder, second_str_wrapper, ptr_type);
|
||||
|
||||
let second_loop = |second_index, second_str_elem| {
|
||||
// The pointer to the element in the combined str.
|
||||
// Note that the pointer does not start at the index
|
||||
// 0, it starts at the index of first_str_len. In that
|
||||
// sense it is "offset".
|
||||
let offset_combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(combined_str_ptr, &[first_str_len], "elem")
|
||||
};
|
||||
|
||||
// The pointer to the char from the second str
|
||||
// in the combined list
|
||||
let combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
offset_combined_str_char_ptr,
|
||||
&[second_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_char_ptr, second_str_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
second_str_ptr,
|
||||
second_str_len,
|
||||
index_name,
|
||||
second_loop,
|
||||
);
|
||||
|
||||
store_list(env, combined_str_ptr, combined_str_len)
|
||||
};
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
second_str_length_comparison,
|
||||
if_second_str_is_not_empty,
|
||||
if_second_str_is_empty,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
};
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
first_str_length_comparison,
|
||||
if_first_str_is_not_empty,
|
||||
if_first_str_is_empty,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_int_binop<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
lhs: IntValue<'ctx>,
|
||||
|
@ -2449,12 +2434,32 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(),
|
||||
NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(),
|
||||
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
|
||||
NumPowInt => call_bitcode_fn(NumPowInt, env, &[lhs.into(), rhs.into()], "pow_int_"),
|
||||
_ => {
|
||||
unreachable!("Unrecognized int binary operation: {:?}", op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call_bitcode_fn<'a, 'ctx, 'env>(
|
||||
op: LowLevel,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
fn_name: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let fn_val = env
|
||||
.module
|
||||
.get_function(fn_name)
|
||||
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name));
|
||||
let call = env.builder.build_call(fn_val, args, "call_builtin");
|
||||
|
||||
call.set_call_convention(fn_val.get_call_conventions());
|
||||
|
||||
call.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op))
|
||||
}
|
||||
|
||||
fn build_float_binop<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
lhs: FloatValue<'ctx>,
|
||||
|
@ -2478,11 +2483,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
|
|||
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
|
||||
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
|
||||
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
|
||||
NumPow => call_intrinsic(
|
||||
LLVM_POW_F64,
|
||||
env,
|
||||
&[(lhs.into(), _lhs_layout), (rhs.into(), _rhs_layout)],
|
||||
),
|
||||
NumPow => env.call_intrinsic(LLVM_POW_F64, &[lhs.into(), rhs.into()]),
|
||||
_ => {
|
||||
unreachable!("Unrecognized int binary operation: {:?}", op);
|
||||
}
|
||||
|
@ -2540,22 +2541,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
|||
}
|
||||
NumToFloat => {
|
||||
// TODO specialize this to be not just for i64!
|
||||
let builtin_fn_name = "i64_to_f64_";
|
||||
|
||||
let fn_val = env
|
||||
.module
|
||||
.get_function(builtin_fn_name)
|
||||
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", builtin_fn_name));
|
||||
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(fn_val, &[arg.into()], "call_builtin");
|
||||
|
||||
call.set_call_convention(fn_val.get_call_conventions());
|
||||
|
||||
call.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op))
|
||||
call_bitcode_fn(NumToFloat, env, &[arg.into()], "i64_to_f64_")
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Unrecognized int unary operation: {:?}", op);
|
||||
|
@ -2566,7 +2552,6 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
|||
fn build_float_unary_op<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
arg: FloatValue<'ctx>,
|
||||
arg_layout: &Layout<'a>,
|
||||
op: LowLevel,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_module::low_level::LowLevel::*;
|
||||
|
@ -2575,18 +2560,24 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
|
|||
|
||||
match op {
|
||||
NumNeg => bd.build_float_neg(arg, "negate_float").into(),
|
||||
NumAbs => call_intrinsic(LLVM_FABS_F64, env, &[(arg.into(), arg_layout)]),
|
||||
NumSqrtUnchecked => call_intrinsic(LLVM_SQRT_F64, env, &[(arg.into(), arg_layout)]),
|
||||
NumRound => call_intrinsic(LLVM_LROUND_I64_F64, env, &[(arg.into(), arg_layout)]),
|
||||
NumSin => call_intrinsic(LLVM_SIN_F64, env, &[(arg.into(), arg_layout)]),
|
||||
NumCos => call_intrinsic(LLVM_COS_F64, env, &[(arg.into(), arg_layout)]),
|
||||
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
|
||||
NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]),
|
||||
NumRound => env.call_intrinsic(LLVM_LROUND_I64_F64, &[arg.into()]),
|
||||
NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]),
|
||||
NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]),
|
||||
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */
|
||||
NumCeiling => env.builder.build_cast(
|
||||
InstructionOpcode::FPToSI,
|
||||
call_intrinsic(LLVM_CEILING_F64, env, &[(arg.into(), arg_layout)]),
|
||||
env.call_intrinsic(LLVM_CEILING_F64, &[arg.into()]),
|
||||
env.context.i64_type(),
|
||||
"num_ceiling",
|
||||
),
|
||||
NumFloor => env.builder.build_cast(
|
||||
InstructionOpcode::FPToSI,
|
||||
env.call_intrinsic(LLVM_FLOOR_F64, &[arg.into()]),
|
||||
env.context.i64_type(),
|
||||
"num_floor",
|
||||
),
|
||||
_ => {
|
||||
unreachable!("Unrecognized int unary operation: {:?}", op);
|
||||
}
|
||||
|
|
|
@ -236,7 +236,7 @@ pub fn list_join<'a, 'ctx, 'env>(
|
|||
// outer_list_len > 0
|
||||
// We do this check to avoid allocating memory. If the input
|
||||
// list is empty, then we can just return an empty list.
|
||||
let comparison = list_is_not_empty(builder, ctx, outer_list_len);
|
||||
let comparison = list_is_not_empty(env, outer_list_len);
|
||||
|
||||
let build_then = || {
|
||||
let list_len_sum_name = "#listslengthsum";
|
||||
|
@ -286,7 +286,7 @@ pub fn list_join<'a, 'ctx, 'env>(
|
|||
let inner_list_len = list_len(builder, inner_list_wrapper);
|
||||
|
||||
// inner_list_len > 0
|
||||
let inner_list_comparison = list_is_not_empty(builder, ctx, inner_list_len);
|
||||
let inner_list_comparison = list_is_not_empty(env, inner_list_len);
|
||||
|
||||
let inner_list_non_empty_block =
|
||||
ctx.append_basic_block(parent, "inner_list_non_empty");
|
||||
|
@ -1125,14 +1125,13 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
|||
// first_list_len > 0
|
||||
// We do this check to avoid allocating memory. If the first input
|
||||
// list is empty, then we can just return the second list cloned
|
||||
let first_list_length_comparison = list_is_not_empty(builder, ctx, first_list_len);
|
||||
let first_list_length_comparison = list_is_not_empty(env, first_list_len);
|
||||
|
||||
let if_first_list_is_empty = || {
|
||||
// second_list_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// list is empty, then we can just return an empty list
|
||||
let second_list_length_comparison =
|
||||
list_is_not_empty(builder, ctx, second_list_len);
|
||||
let second_list_length_comparison = list_is_not_empty(env, second_list_len);
|
||||
|
||||
let build_second_list_then = || {
|
||||
let elem_type =
|
||||
|
@ -1181,8 +1180,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
|||
// second_list_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// list is empty, then we can just return the first list cloned
|
||||
let second_list_length_comparison =
|
||||
list_is_not_empty(builder, ctx, second_list_len);
|
||||
let second_list_length_comparison = list_is_not_empty(env, second_list_len);
|
||||
|
||||
let if_second_list_is_not_empty = || {
|
||||
let combined_list_len =
|
||||
|
@ -1194,6 +1192,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
|||
let first_list_ptr = load_list_ptr(builder, first_list_wrapper, ptr_type);
|
||||
|
||||
// FIRST LOOP
|
||||
// TODO when the element type supports it, replace FIRST_LOOP with a memcpy!
|
||||
let first_loop = |first_index, first_list_elem| {
|
||||
// The pointer to the element in the combined list
|
||||
let combined_list_elem_ptr = unsafe {
|
||||
|
@ -1226,6 +1225,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
|||
let second_list_ptr = load_list_ptr(builder, second_list_wrapper, ptr_type);
|
||||
|
||||
// SECOND LOOP
|
||||
// TODO when the element type supports it, replace SECOND_LOOP with a memcpy!
|
||||
let second_loop = |second_index, second_list_elem| {
|
||||
// The pointer to the element in the combined list.
|
||||
// Note that the pointer does not start at the index
|
||||
|
@ -1561,16 +1561,12 @@ pub fn empty_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'
|
|||
BasicValueEnum::StructValue(struct_type.const_zero())
|
||||
}
|
||||
|
||||
pub fn list_is_not_empty<'ctx>(
|
||||
builder: &Builder<'ctx>,
|
||||
ctx: &'ctx Context,
|
||||
list_len: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
builder.build_int_compare(
|
||||
pub fn list_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> IntValue<'ctx> {
|
||||
env.builder.build_int_compare(
|
||||
IntPredicate::UGT,
|
||||
list_len,
|
||||
ctx.i64_type().const_int(0, false),
|
||||
"greaterthanzero",
|
||||
len,
|
||||
env.ptr_int().const_zero(),
|
||||
"list_len_is_nonzero",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
593
compiler/gen/src/llvm/build_str.rs
Normal file
593
compiler/gen/src/llvm/build_str.rs
Normal file
|
@ -0,0 +1,593 @@
|
|||
use crate::llvm::build::{ptr_from_symbol, Env, InPlace, Scope};
|
||||
use crate::llvm::build_list::{
|
||||
allocate_list, build_basic_phi2, empty_list, incrementing_elem_loop, load_list_ptr, store_list,
|
||||
};
|
||||
use crate::llvm::convert::{collection, ptr_int};
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
|
||||
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
|
||||
|
||||
/// Str.concat : Str, Str -> Str
|
||||
pub fn str_concat<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
first_str_symbol: Symbol,
|
||||
second_str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
|
||||
let second_str_ptr = ptr_from_symbol(scope, second_str_symbol);
|
||||
let first_str_ptr = ptr_from_symbol(scope, first_str_symbol);
|
||||
|
||||
let str_wrapper_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
|
||||
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*second_str_ptr,
|
||||
str_wrapper_type,
|
||||
|second_str_ptr, second_str_len, second_str_smallness| {
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*first_str_ptr,
|
||||
str_wrapper_type,
|
||||
|first_str_ptr, first_str_len, first_str_smallness| {
|
||||
// first_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the first input
|
||||
// str is empty, then we can just return the second str cloned
|
||||
let first_str_length_comparison = str_is_not_empty(env, first_str_len);
|
||||
|
||||
let if_first_str_is_empty = || {
|
||||
// second_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// str is empty, then we can just return an empty str
|
||||
let second_str_length_comparison = str_is_not_empty(env, second_str_len);
|
||||
|
||||
let if_second_str_is_nonempty = || {
|
||||
let (new_wrapper, _) = clone_nonempty_str(
|
||||
env,
|
||||
inplace,
|
||||
second_str_smallness,
|
||||
second_str_len,
|
||||
second_str_ptr,
|
||||
);
|
||||
|
||||
BasicValueEnum::StructValue(new_wrapper)
|
||||
};
|
||||
|
||||
let if_second_str_is_empty = || empty_list(env);
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
second_str_length_comparison,
|
||||
if_second_str_is_nonempty,
|
||||
if_second_str_is_empty,
|
||||
str_wrapper_type,
|
||||
)
|
||||
};
|
||||
|
||||
let if_first_str_is_not_empty = || {
|
||||
let if_second_str_is_empty = || {
|
||||
let (new_wrapper, _) = clone_nonempty_str(
|
||||
env,
|
||||
inplace,
|
||||
first_str_smallness,
|
||||
first_str_len,
|
||||
first_str_ptr,
|
||||
);
|
||||
|
||||
BasicValueEnum::StructValue(new_wrapper)
|
||||
};
|
||||
|
||||
// second_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// str is empty, then we can just return the first str cloned
|
||||
let second_str_length_comparison = str_is_not_empty(env, second_str_len);
|
||||
|
||||
let if_second_str_is_not_empty = || {
|
||||
let combined_str_len = builder.build_int_add(
|
||||
first_str_len,
|
||||
second_str_len,
|
||||
"add_list_lengths",
|
||||
);
|
||||
|
||||
// The combined string is big iff its length is
|
||||
// greater than or equal to the size in memory
|
||||
// of a small str (e.g. len >= 16 on 64-bit targets)
|
||||
let is_big = env.builder.build_int_compare(
|
||||
IntPredicate::UGE,
|
||||
combined_str_len,
|
||||
env.ptr_int().const_int(env.small_str_bytes() as u64, false),
|
||||
"str_is_big",
|
||||
);
|
||||
|
||||
let if_big = || {
|
||||
let combined_str_ptr =
|
||||
allocate_list(env, inplace, &CHAR_LAYOUT, combined_str_len);
|
||||
|
||||
// TODO replace FIRST_LOOP with a memcpy!
|
||||
// FIRST LOOP
|
||||
let first_loop = |first_index, first_str_elem| {
|
||||
// The pointer to the element in the combined list
|
||||
let combined_str_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_elem_ptr, first_str_elem);
|
||||
};
|
||||
|
||||
let index_name = "#index";
|
||||
|
||||
let index_alloca = incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
first_str_ptr,
|
||||
first_str_len,
|
||||
index_name,
|
||||
first_loop,
|
||||
);
|
||||
|
||||
// Reset the index variable to 0
|
||||
builder
|
||||
.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||
|
||||
// TODO replace SECOND_LOOP with a memcpy!
|
||||
// SECOND LOOP
|
||||
let second_loop = |second_index, second_str_elem| {
|
||||
// The pointer to the element in the combined str.
|
||||
// Note that the pointer does not start at the index
|
||||
// 0, it starts at the index of first_str_len. In that
|
||||
// sense it is "offset".
|
||||
let offset_combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_str_len],
|
||||
"elem",
|
||||
)
|
||||
};
|
||||
|
||||
// The pointer to the char from the second str
|
||||
// in the combined list
|
||||
let combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
offset_combined_str_char_ptr,
|
||||
&[second_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_char_ptr, second_str_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
second_str_ptr,
|
||||
second_str_len,
|
||||
index_name,
|
||||
second_loop,
|
||||
);
|
||||
|
||||
store_list(env, combined_str_ptr, combined_str_len)
|
||||
};
|
||||
|
||||
let if_small = || {
|
||||
let combined_str_ptr = builder.build_array_alloca(
|
||||
ctx.i8_type(),
|
||||
ctx.i8_type().const_int(env.small_str_bytes() as u64, false),
|
||||
"alloca_small_str",
|
||||
);
|
||||
|
||||
// TODO replace FIRST_LOOP with a memcpy!
|
||||
// FIRST LOOP
|
||||
let first_loop = |first_index, first_str_elem| {
|
||||
// The pointer to the element in the combined list
|
||||
let combined_str_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_elem_ptr, first_str_elem);
|
||||
};
|
||||
|
||||
let index_name = "#index";
|
||||
|
||||
let index_alloca = incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
first_str_ptr,
|
||||
first_str_len,
|
||||
index_name,
|
||||
first_loop,
|
||||
);
|
||||
|
||||
// Reset the index variable to 0
|
||||
builder
|
||||
.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||
|
||||
// TODO replace SECOND_LOOP with a memcpy!
|
||||
// SECOND LOOP
|
||||
let second_loop = |second_index, second_str_elem| {
|
||||
// The pointer to the element in the combined str.
|
||||
// Note that the pointer does not start at the index
|
||||
// 0, it starts at the index of first_str_len. In that
|
||||
// sense it is "offset".
|
||||
let offset_combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_str_len],
|
||||
"elem",
|
||||
)
|
||||
};
|
||||
|
||||
// The pointer to the char from the second str
|
||||
// in the combined list
|
||||
let combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
offset_combined_str_char_ptr,
|
||||
&[second_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_char_ptr, second_str_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
second_str_ptr,
|
||||
second_str_len,
|
||||
index_name,
|
||||
second_loop,
|
||||
);
|
||||
|
||||
let final_byte = builder.build_int_cast(
|
||||
combined_str_len,
|
||||
ctx.i8_type(),
|
||||
"str_len_to_i8",
|
||||
);
|
||||
|
||||
let final_byte = builder.build_or(
|
||||
final_byte,
|
||||
ctx.i8_type().const_int(0b1000_0000, false),
|
||||
"str_len_set_discriminant",
|
||||
);
|
||||
|
||||
let final_byte_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[ctx
|
||||
.i8_type()
|
||||
.const_int(env.small_str_bytes() as u64 - 1, false)],
|
||||
"str_literal_final_byte",
|
||||
)
|
||||
};
|
||||
|
||||
builder.build_store(final_byte_ptr, final_byte);
|
||||
|
||||
builder.build_load(
|
||||
builder
|
||||
.build_bitcast(
|
||||
combined_str_ptr,
|
||||
collection(ctx, env.ptr_bytes)
|
||||
.ptr_type(AddressSpace::Generic),
|
||||
"cast_collection",
|
||||
)
|
||||
.into_pointer_value(),
|
||||
"small_str_array",
|
||||
)
|
||||
};
|
||||
|
||||
// If the combined length fits in a small string,
|
||||
// write into a small string!
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
is_big,
|
||||
// the result of a Str.concat is most likely big
|
||||
if_big,
|
||||
if_small,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
};
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
second_str_length_comparison,
|
||||
if_second_str_is_not_empty,
|
||||
if_second_str_is_empty,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
};
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
first_str_length_comparison,
|
||||
if_first_str_is_not_empty,
|
||||
if_first_str_is_empty,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Obtain the string's length, cast from i8 to usize
|
||||
fn str_len_from_final_byte<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
final_byte: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let bitmask = ctx.i8_type().const_int(0b0111_1111, false);
|
||||
let len_i8 = builder.build_and(final_byte, bitmask, "small_str_length");
|
||||
|
||||
builder.build_int_cast(len_i8, env.ptr_int(), "len_as_usize")
|
||||
}
|
||||
|
||||
/// Used by LowLevel::StrIsEmpty
|
||||
pub fn str_len<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
|
||||
let if_small = |final_byte| {
|
||||
let len = str_len_from_final_byte(env, final_byte);
|
||||
|
||||
BasicValueEnum::IntValue(len)
|
||||
};
|
||||
|
||||
let if_big = |_| {
|
||||
let len = big_str_len(
|
||||
builder,
|
||||
builder
|
||||
.build_load(wrapper_ptr, "big_str")
|
||||
.into_struct_value(),
|
||||
);
|
||||
|
||||
BasicValueEnum::IntValue(len)
|
||||
};
|
||||
|
||||
if_small_str(
|
||||
env,
|
||||
parent,
|
||||
wrapper_ptr,
|
||||
if_small,
|
||||
if_big,
|
||||
BasicTypeEnum::IntType(env.ptr_int()),
|
||||
)
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
fn load_str<'a, 'ctx, 'env, Callback>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
cb: Callback,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
Callback: Fn(PointerValue<'ctx>, IntValue<'ctx>, Smallness) -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
let builder = env.builder;
|
||||
|
||||
let if_small = |final_byte| {
|
||||
cb(
|
||||
cast_str_wrapper_to_array(env, wrapper_ptr),
|
||||
str_len_from_final_byte(env, final_byte),
|
||||
Smallness::Small,
|
||||
)
|
||||
};
|
||||
|
||||
let if_big = |wrapper_struct| {
|
||||
let list_ptr = load_list_ptr(
|
||||
builder,
|
||||
wrapper_struct,
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
);
|
||||
|
||||
cb(
|
||||
list_ptr,
|
||||
big_str_len(builder, wrapper_struct),
|
||||
Smallness::Big,
|
||||
)
|
||||
};
|
||||
|
||||
if_small_str(env, parent, wrapper_ptr, if_small, if_big, ret_type)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Smallness {
|
||||
Small,
|
||||
Big,
|
||||
}
|
||||
|
||||
fn clone_nonempty_str<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
smallness: Smallness,
|
||||
len: IntValue<'ctx>,
|
||||
bytes_ptr: PointerValue<'ctx>,
|
||||
) -> (StructValue<'ctx>, PointerValue<'ctx>) {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let ptr_bytes = env.ptr_bytes;
|
||||
|
||||
// Allocate space for the new str that we'll copy into.
|
||||
match smallness {
|
||||
Smallness::Small => {
|
||||
let wrapper_struct_ptr = cast_str_bytes_to_wrapper(env, bytes_ptr);
|
||||
let wrapper_struct = builder.build_load(wrapper_struct_ptr, "str_wrapper");
|
||||
let alloca = builder.build_alloca(collection(ctx, ptr_bytes), "small_str_clone");
|
||||
|
||||
builder.build_store(alloca, wrapper_struct);
|
||||
|
||||
(wrapper_struct.into_struct_value(), alloca)
|
||||
}
|
||||
Smallness::Big => {
|
||||
let clone_ptr = allocate_list(env, inplace, &CHAR_LAYOUT, len);
|
||||
let int_type = ptr_int(ctx, ptr_bytes);
|
||||
let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr");
|
||||
|
||||
// TODO check if malloc returned null; if so, runtime error for OOM!
|
||||
|
||||
// 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);
|
||||
|
||||
// Create a fresh wrapper struct for the newly populated array
|
||||
let struct_type = collection(ctx, env.ptr_bytes);
|
||||
let mut struct_val;
|
||||
|
||||
// Store the pointer
|
||||
struct_val = builder
|
||||
.build_insert_value(
|
||||
struct_type.get_undef(),
|
||||
ptr_as_int,
|
||||
Builtin::WRAPPER_PTR,
|
||||
"insert_ptr",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Store the length
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
|
||||
.unwrap();
|
||||
|
||||
let answer = builder
|
||||
.build_bitcast(
|
||||
struct_val.into_struct_value(),
|
||||
collection(ctx, ptr_bytes),
|
||||
"cast_collection",
|
||||
)
|
||||
.into_struct_value();
|
||||
|
||||
(answer, clone_ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cast_str_bytes_to_wrapper<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
bytes_ptr: PointerValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let struct_ptr_type = collection(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic);
|
||||
|
||||
env.builder
|
||||
.build_bitcast(bytes_ptr, struct_ptr_type, "str_as_struct_ptr")
|
||||
.into_pointer_value()
|
||||
}
|
||||
|
||||
fn cast_str_wrapper_to_array<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let array_ptr_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
||||
|
||||
env.builder
|
||||
.build_bitcast(wrapper_ptr, array_ptr_type, "str_as_array_ptr")
|
||||
.into_pointer_value()
|
||||
}
|
||||
|
||||
fn if_small_str<'a, 'ctx, 'env, IfSmallFn, IfBigFn>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
mut if_small: IfSmallFn,
|
||||
mut if_big: IfBigFn,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
IfSmallFn: FnMut(IntValue<'ctx>) -> BasicValueEnum<'ctx>,
|
||||
IfBigFn: FnMut(StructValue<'ctx>) -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let byte_array_ptr = cast_str_wrapper_to_array(env, wrapper_ptr);
|
||||
let final_byte_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
byte_array_ptr,
|
||||
&[ctx
|
||||
.i8_type()
|
||||
.const_int(env.small_str_bytes() as u64 - 1, false)],
|
||||
"final_byte_ptr",
|
||||
)
|
||||
};
|
||||
|
||||
let final_byte = builder
|
||||
.build_load(final_byte_ptr, "load_final_byte")
|
||||
.into_int_value();
|
||||
|
||||
let bitmask = ctx.i8_type().const_int(0b1000_0000, false);
|
||||
|
||||
let is_small_i8 = builder.build_int_compare(
|
||||
IntPredicate::NE,
|
||||
ctx.i8_type().const_zero(),
|
||||
builder.build_and(final_byte, bitmask, "is_small"),
|
||||
"is_small_comparison",
|
||||
);
|
||||
|
||||
let is_small = builder.build_int_cast(is_small_i8, ctx.bool_type(), "is_small_as_bool");
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
is_small,
|
||||
|| if_small(final_byte),
|
||||
|| {
|
||||
if_big(
|
||||
builder
|
||||
.build_load(wrapper_ptr, "load_wrapper_struct")
|
||||
.into_struct_value(),
|
||||
)
|
||||
},
|
||||
ret_type,
|
||||
)
|
||||
}
|
||||
|
||||
fn big_str_len<'ctx>(builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>) -> IntValue<'ctx> {
|
||||
builder
|
||||
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "big_str_len")
|
||||
.unwrap()
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
fn str_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> IntValue<'ctx> {
|
||||
env.builder.build_int_compare(
|
||||
IntPredicate::UGT,
|
||||
len,
|
||||
env.ptr_int().const_zero(),
|
||||
"str_len_is_nonzero",
|
||||
)
|
||||
}
|
Binary file not shown.
|
@ -1,5 +1,6 @@
|
|||
pub mod build;
|
||||
pub mod build_list;
|
||||
pub mod build_str;
|
||||
pub mod compare;
|
||||
pub mod convert;
|
||||
pub mod refcounting;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue