Merge remote-tracking branch 'origin/trunk' into exception-handling

This commit is contained in:
Folkert 2020-09-19 21:50:57 +02:00
commit 16fc5dd497
23 changed files with 1280 additions and 351 deletions

View file

@ -75,7 +75,7 @@ fn jit_to_ast_help<'a>(
execution_engine,
main_fn_name,
&'static str,
|string: &'static str| { str_slice_to_ast(env.arena, env.arena.alloc(string)) }
|string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) }
),
Layout::Builtin(Builtin::EmptyList) => {
run_jit_function!(execution_engine, main_fn_name, &'static str, |_| {
@ -228,7 +228,7 @@ fn ptr_to_ast<'a>(
Layout::Builtin(Builtin::Str) => {
let arena_str = unsafe { *(ptr as *const &'static str) };
str_slice_to_ast(env.arena, arena_str)
str_to_ast(env.arena, arena_str)
}
Layout::Struct(field_layouts) => match content {
Content::Structure(FlatType::Record(fields, _)) => {
@ -463,6 +463,28 @@ fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
#[cfg(target_endian = "little")]
#[cfg(target_pointer_width = "64")]
/// TODO implement this for 32-bit and big-endian targets. NOTE: As of this writing,
/// we don't have big-endian small strings implemented yet!
fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> {
let bytes: [u8; 16] = unsafe { std::mem::transmute::<&'a str, [u8; 16]>(string) };
let is_small = (bytes[15] & 0b1000_0000) != 0;
if is_small {
let len = (bytes[15] & 0b0111_1111) as usize;
let mut string = bumpalo::collections::String::with_capacity_in(len, arena);
for byte in bytes.iter().take(len) {
string.push(*byte as char);
}
str_slice_to_ast(arena, arena.alloc(string))
} else {
str_slice_to_ast(arena, string)
}
}
fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
if string.contains('\n') {
todo!(

View file

@ -21,7 +21,7 @@ mod cli_run {
]);
assert_eq!(&out.stderr, "");
assert!(&out.stdout.ends_with("Hello, World!\n"));
assert!(&out.stdout.ends_with("Hello, World!!!!!!!!!!!!!\n"));
assert!(out.status.success());
}

View file

@ -33,6 +33,8 @@ the `cargo rustc` command above to regenerate it.
> $ cargo rustc --release --lib -- --emit=llvm-ir
> ```
**Note**: In order to be able to address the bitcode functions by name, they need to be defined with the `#[no_mangle]` attribute.
## Importing the bitcode
The bitcode is a bunch of bytes that aren't particularly human-readable.
@ -54,3 +56,7 @@ compiler/gen/src/llvm/builtins.bc
Once that's done, `git status` should show that the `builtins.bc` file
has been changed. Commit that change and you're done!
## Calling bitcode functions
Use the `call_bitcode_fn` function defined in `llvm/src/build.rs` to call bitcode funcitons.

View file

@ -10,3 +10,29 @@
pub fn i64_to_f64_(num: i64) -> f64 {
num as f64
}
/// Adapted from Rust's core::num module, by the Rust core team,
/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
///
/// Thank you, Rust core team!
#[no_mangle]
pub fn pow_int_(mut base: i64, mut exp: i64) -> i64 {
let mut acc = 1;
while exp > 1 {
if (exp & 1) == 1 {
acc *= base;
}
exp /= 2;
base *= base;
}
// Deal with the final bit of the exponent separately, since
// squaring the base afterwards is not necessary and may cause a
// needless overflow.
if exp == 1 {
acc *= base;
}
acc
}

View file

@ -1,5 +1,4 @@
interface Str exposes [ Str, isEmpty, join ] imports []
## Types
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks

View file

@ -424,6 +424,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Func(vec![float_type()], Box::new(int_type())),
);
// powInt : Int, Int -> Int
add_type(
Symbol::NUM_POW_INT,
SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())),
);
// floor : Float -> Int
add_type(
Symbol::NUM_FLOOR,
SolvedType::Func(vec![float_type()], Box::new(int_type())),
);
// Bool module
// and : Bool, Bool -> Bool
@ -460,7 +472,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// isEmpty : Str -> Bool
add_type(
Symbol::STR_ISEMPTY,
Symbol::STR_IS_EMPTY,
SolvedType::Func(vec![str_type()], Box::new(bool_type())),
);

View file

@ -463,6 +463,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![float_type(star1)], int_type(star2))
});
// powInt : Int, Int -> Int
add_type(Symbol::NUM_POW_INT, {
let_tvars! { star1, star2, star3 };
unique_function(vec![int_type(star1), int_type(star2)], int_type(star3))
});
// floor : Float -> Int
add_type(Symbol::NUM_FLOOR, {
let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], int_type(star2))
});
// Bool module
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool
@ -1074,7 +1086,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Str module
// isEmpty : Attr * Str -> Attr * Bool
add_type(Symbol::STR_ISEMPTY, {
add_type(Symbol::STR_IS_EMPTY, {
let_tvars! { star1, star2 };
unique_function(vec![str_type(star1)], bool_type(star2))
});

View file

@ -51,6 +51,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::BOOL_OR => bool_or,
Symbol::BOOL_NOT => bool_not,
Symbol::STR_CONCAT => str_concat,
Symbol::STR_IS_EMPTY => str_is_empty,
Symbol::LIST_LEN => list_len,
Symbol::LIST_GET => list_get,
Symbol::LIST_SET => list_set,
@ -92,6 +93,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_TO_FLOAT => num_to_float,
Symbol::NUM_POW => num_pow,
Symbol::NUM_CEILING => num_ceiling,
Symbol::NUM_POW_INT => num_pow_int,
Symbol::NUM_FLOOR => num_floor,
}
}
@ -621,6 +624,46 @@ fn num_ceiling(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_var,
)
}
/// Num.powInt : Int, Int -> Int
fn num_pow_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumPowInt,
args: vec![(int_var, Var(Symbol::ARG_1)), (int_var, Var(Symbol::ARG_2))],
ret_var: int_var,
};
defn(
symbol,
vec![(int_var, Symbol::ARG_1), (int_var, Symbol::ARG_2)],
var_store,
body,
int_var,
)
}
/// Num.floor : Float -> Int
fn num_floor(symbol: Symbol, var_store: &mut VarStore) -> Def {
let float_var = var_store.fresh();
let int_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumFloor,
args: vec![(float_var, Var(Symbol::ARG_1))],
ret_var: int_var,
};
defn(
symbol,
vec![(float_var, Symbol::ARG_1)],
var_store,
body,
int_var,
)
}
/// List.isEmpty : List * -> Bool
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -691,6 +734,26 @@ fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Str.isEmpty : List * -> Bool
fn str_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
let bool_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::StrIsEmpty,
args: vec![(str_var, Var(Symbol::ARG_1))],
ret_var: bool_var,
};
defn(
symbol,
vec![(str_var, Symbol::ARG_1)],
var_store,
body,
bool_var,
)
}
/// List.concat : List elem, List elem -> List elem
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();

View file

@ -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,21 +636,11 @@ 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 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)
// TODO check if malloc returned null; if so, runtime error for OOM!
};
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
@ -549,12 +654,75 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
builder.build_store(elem_ptr, val);
}
};
let ptr_bytes = env.ptr_bytes;
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);
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));
let mut struct_val;
// Store the pointer
@ -572,19 +740,20 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
// Bitcast to an array of raw bytes
populate_str(ptr);
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);
}

View file

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

View 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.

View file

@ -1,5 +1,6 @@
pub mod build;
pub mod build_list;
pub mod build_str;
pub mod compare;
pub mod convert;
pub mod refcounting;

View file

@ -673,6 +673,16 @@ mod gen_num {
#[test]
fn ceiling() {
assert_evals_to!("Num.ceiling 1.5", 2, i64);
assert_evals_to!("Num.ceiling 1.1", 2, i64);
}
#[test]
fn floor() {
assert_evals_to!("Num.floor 1.9", 1, i64);
}
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
}

View file

@ -13,13 +13,6 @@ mod helpers;
#[cfg(test)]
mod gen_primitives {
#[test]
fn basic_str() {
assert_evals_to!("\"\"", "", &'static str);
assert_evals_to!("\"shirt and hat\"", "shirt and hat", &'static str);
}
#[test]
fn basic_int() {
assert_evals_to!("123", 123, i64);

View file

@ -14,11 +14,7 @@ mod helpers;
#[cfg(test)]
mod gen_str {
#[test]
fn str_concat() {
assert_evals_to!("Str.concat \"a\" \"b\"", "ab", &'static str);
assert_evals_to!("Str.concat \"\" \"second str\"", "second str", &'static str);
assert_evals_to!("Str.concat \"first str\" \"\"", "first str", &'static str);
assert_evals_to!("Str.concat \"\" \"\"", "", &'static str);
fn str_concat_big_to_big() {
assert_evals_to!(
indoc!(
r#"
@ -31,4 +27,179 @@ mod gen_str {
&'static str
);
}
#[test]
fn small_str_literal() {
assert_evals_to!(
"\"JJJJJJJJJJJJJJJ\"",
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_zeroed_literal() {
// Verifies that we zero out unused bytes in the string.
// This is important so that string equality tests don't randomly
// fail due to unused memory being there!
assert_evals_to!(
"\"J\"",
[
0x4a,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0b1000_0001
],
[u8; 16]
);
}
#[test]
fn small_str_concat_empty_first_arg() {
assert_evals_to!(
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_empty_second_arg() {
assert_evals_to!(
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_small_to_big() {
assert_evals_to!(
r#"Str.concat "abc" " this is longer than 15 chars""#,
"abc this is longer than 15 chars",
&'static str
);
}
#[test]
fn small_str_concat_small_to_small_staying_small() {
assert_evals_to!(
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_small_to_small_overflow_to_big() {
assert_evals_to!(
r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
"abcdefghijklmnopqrstuvwxyz",
&'static str
);
}
#[test]
fn str_concat_empty() {
assert_evals_to!(r#"Str.concat "" """#, "", &'static str);
}
#[test]
fn small_str_is_empty() {
assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
}
#[test]
fn big_str_is_empty() {
assert_evals_to!(
r#"Str.isEmpty "this is more than 15 chars long""#,
false,
bool
);
}
#[test]
fn empty_str_is_empty() {
assert_evals_to!(r#"Str.isEmpty """#, true, bool);
}
}

View file

@ -4,6 +4,7 @@
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LowLevel {
StrConcat,
StrIsEmpty,
ListLen,
ListGetUnsafe,
ListSet,
@ -37,6 +38,8 @@ pub enum LowLevel {
NumToFloat,
NumPow,
NumCeiling,
NumPowInt,
NumFloor,
Eq,
NotEq,
And,

View file

@ -642,6 +642,8 @@ define_builtins! {
37 NUM_COMPARE: "compare"
38 NUM_POW: "pow"
39 NUM_CEILING: "ceiling"
40 NUM_POW_INT: "powInt"
41 NUM_FLOOR: "floor"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
@ -655,7 +657,7 @@ define_builtins! {
3 STR: "Str" => {
0 STR_STR: "Str" imported // the Str.Str type alias
1 STR_AT_STR: "@Str" // the Str.@Str private tag
2 STR_ISEMPTY: "isEmpty"
2 STR_IS_EMPTY: "isEmpty"
3 STR_APPEND: "append"
4 STR_CONCAT: "concat"
}

View file

@ -505,7 +505,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// - arguments that we may want to update destructively must be Owned
// - other refcounted arguments are Borrowed
match op {
ListLen => arena.alloc_slice_copy(&[borrowed]),
ListLen | StrIsEmpty => arena.alloc_slice_copy(&[borrowed]),
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
@ -522,11 +522,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow => {
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt => {
arena.alloc_slice_copy(&[irrelevant, irrelevant])
}
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumToFloat
| NumCeiling | Not => arena.alloc_slice_copy(&[irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not => arena.alloc_slice_copy(&[irrelevant]),
}
}

View file

@ -2382,6 +2382,18 @@ mod solve_expr {
);
}
#[test]
fn pow() {
infer_eq_without_problem(
indoc!(
r#"
Num.pow
"#
),
"Float, Float -> Float",
);
}
#[test]
fn ceiling() {
infer_eq_without_problem(
@ -2395,14 +2407,26 @@ mod solve_expr {
}
#[test]
fn pow() {
fn floor() {
infer_eq_without_problem(
indoc!(
r#"
Num.pow
Num.floor
"#
),
"Float, Float -> Float",
"Float -> Int",
);
}
#[test]
fn pow_int() {
infer_eq_without_problem(
indoc!(
r#"
Num.powInt
"#
),
"Int, Int -> Int",
);
}

View file

@ -27,6 +27,11 @@ These are potentially inspirational resources for the editor's design.
* [Xi](https://xi-editor.io/) modern text editor with concurrent editing (related to [Druid](https://github.com/linebender/druid))
* [Self](https://selflanguage.org/) programming language
### Debugging
* [VS code debug visualization](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer)
* [Algorithm visualization for javascript](https://algorithm-visualizer.org)
### Structured Editing
* [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others

View file

@ -1,7 +1,7 @@
app Hello provides [ main ] imports []
greeting =
hi = "Hello, World!"
hi = "Hello, World!!!!!!!!!!!!!"
hi

View file

@ -11,9 +11,9 @@ let
in { pkgs ? pinnedPkgs }:
let
isOsX = builtins.currentSystem == "x86_64-darwin";
darwin-frameworks = if isOsX then
darwin-frameworks =
if isOsX then
with pkgs.darwin.apple_sdk.frameworks; [
AppKit
CoreFoundation
@ -30,7 +30,7 @@ let
[
pkgs.rustup
pkgs.cargo
pkgs.llvm_10
llvm
# libraries for llvm
pkgs.libffi
pkgs.libxml2