mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +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
|
@ -75,7 +75,7 @@ fn jit_to_ast_help<'a>(
|
||||||
execution_engine,
|
execution_engine,
|
||||||
main_fn_name,
|
main_fn_name,
|
||||||
&'static str,
|
&'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) => {
|
Layout::Builtin(Builtin::EmptyList) => {
|
||||||
run_jit_function!(execution_engine, main_fn_name, &'static str, |_| {
|
run_jit_function!(execution_engine, main_fn_name, &'static str, |_| {
|
||||||
|
@ -228,7 +228,7 @@ fn ptr_to_ast<'a>(
|
||||||
Layout::Builtin(Builtin::Str) => {
|
Layout::Builtin(Builtin::Str) => {
|
||||||
let arena_str = unsafe { *(ptr as *const &'static 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 {
|
Layout::Struct(field_layouts) => match content {
|
||||||
Content::Structure(FlatType::Record(fields, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
|
@ -463,6 +463,28 @@ fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {
|
||||||
Expr::Num(arena.alloc(format!("{}", num)))
|
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> {
|
fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
|
||||||
if string.contains('\n') {
|
if string.contains('\n') {
|
||||||
todo!(
|
todo!(
|
||||||
|
|
|
@ -21,7 +21,7 @@ mod cli_run {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_eq!(&out.stderr, "");
|
assert_eq!(&out.stderr, "");
|
||||||
assert!(&out.stdout.ends_with("Hello, World!\n"));
|
assert!(&out.stdout.ends_with("Hello, World!!!!!!!!!!!!!\n"));
|
||||||
assert!(out.status.success());
|
assert!(out.status.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ the `cargo rustc` command above to regenerate it.
|
||||||
> $ cargo rustc --release --lib -- --emit=llvm-ir
|
> $ 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
|
## Importing the bitcode
|
||||||
|
|
||||||
The bitcode is a bunch of bytes that aren't particularly human-readable.
|
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
|
Once that's done, `git status` should show that the `builtins.bc` file
|
||||||
has been changed. Commit that change and you're done!
|
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.
|
||||||
|
|
|
@ -10,3 +10,29 @@
|
||||||
pub fn i64_to_f64_(num: i64) -> f64 {
|
pub fn i64_to_f64_(num: i64) -> f64 {
|
||||||
num as 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
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
interface Str exposes [ Str, isEmpty, join ] imports []
|
interface Str exposes [ Str, isEmpty, join ] imports []
|
||||||
|
|
||||||
## Types
|
## Types
|
||||||
|
|
||||||
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
|
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
|
||||||
|
|
|
@ -424,6 +424,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
SolvedType::Func(vec![float_type()], Box::new(int_type())),
|
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
|
// Bool module
|
||||||
|
|
||||||
// and : Bool, Bool -> Bool
|
// and : Bool, Bool -> Bool
|
||||||
|
@ -460,7 +472,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
|
|
||||||
// isEmpty : Str -> Bool
|
// isEmpty : Str -> Bool
|
||||||
add_type(
|
add_type(
|
||||||
Symbol::STR_ISEMPTY,
|
Symbol::STR_IS_EMPTY,
|
||||||
SolvedType::Func(vec![str_type()], Box::new(bool_type())),
|
SolvedType::Func(vec![str_type()], Box::new(bool_type())),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -463,6 +463,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
unique_function(vec![float_type(star1)], int_type(star2))
|
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
|
// Bool module
|
||||||
|
|
||||||
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool
|
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool
|
||||||
|
@ -1074,7 +1086,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
// Str module
|
// Str module
|
||||||
|
|
||||||
// isEmpty : Attr * Str -> Attr * Bool
|
// isEmpty : Attr * Str -> Attr * Bool
|
||||||
add_type(Symbol::STR_ISEMPTY, {
|
add_type(Symbol::STR_IS_EMPTY, {
|
||||||
let_tvars! { star1, star2 };
|
let_tvars! { star1, star2 };
|
||||||
unique_function(vec![str_type(star1)], bool_type(star2))
|
unique_function(vec![str_type(star1)], bool_type(star2))
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,6 +51,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
||||||
Symbol::BOOL_OR => bool_or,
|
Symbol::BOOL_OR => bool_or,
|
||||||
Symbol::BOOL_NOT => bool_not,
|
Symbol::BOOL_NOT => bool_not,
|
||||||
Symbol::STR_CONCAT => str_concat,
|
Symbol::STR_CONCAT => str_concat,
|
||||||
|
Symbol::STR_IS_EMPTY => str_is_empty,
|
||||||
Symbol::LIST_LEN => list_len,
|
Symbol::LIST_LEN => list_len,
|
||||||
Symbol::LIST_GET => list_get,
|
Symbol::LIST_GET => list_get,
|
||||||
Symbol::LIST_SET => list_set,
|
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_TO_FLOAT => num_to_float,
|
||||||
Symbol::NUM_POW => num_pow,
|
Symbol::NUM_POW => num_pow,
|
||||||
Symbol::NUM_CEILING => num_ceiling,
|
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,
|
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
|
/// List.isEmpty : List * -> Bool
|
||||||
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let list_var = var_store.fresh();
|
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
|
/// List.concat : List elem, List elem -> List elem
|
||||||
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let list_var = var_store.fresh();
|
let list_var = var_store.fresh();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::layout_id::LayoutIds;
|
use crate::layout_id::LayoutIds;
|
||||||
use crate::llvm::build_list::{
|
use crate::llvm::build_list::{
|
||||||
allocate_list, build_basic_phi2, clone_nonempty_list, empty_list, empty_polymorphic_list,
|
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_get_unsafe,
|
||||||
incrementing_elem_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty,
|
|
||||||
list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set,
|
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::compare::{build_eq, build_neq};
|
||||||
use crate::llvm::convert::{
|
use crate::llvm::convert::{
|
||||||
basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int,
|
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::passes::{PassManager, PassManagerBuilder};
|
||||||
use inkwell::types::{BasicTypeEnum, FunctionType, IntType, StructType};
|
use inkwell::types::{BasicTypeEnum, FunctionType, IntType, StructType};
|
||||||
use inkwell::values::BasicValueEnum::{self, *};
|
use inkwell::values::BasicValueEnum::{self, *};
|
||||||
use inkwell::values::InstructionOpcode;
|
use inkwell::values::{
|
||||||
use inkwell::values::{BasicValue, FloatValue, FunctionValue, IntValue, PointerValue, StructValue};
|
BasicValue, CallSiteValue, FloatValue, FunctionValue, InstructionOpcode, IntValue,
|
||||||
use inkwell::AddressSpace;
|
PointerValue, StructValue,
|
||||||
|
};
|
||||||
use inkwell::OptimizationLevel;
|
use inkwell::OptimizationLevel;
|
||||||
|
use inkwell::{AddressSpace, IntPredicate};
|
||||||
use roc_collections::all::{ImMap, MutSet};
|
use roc_collections::all::{ImMap, MutSet};
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::symbol::{Interns, Symbol};
|
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> {
|
pub fn ptr_int(&self) -> IntType<'ctx> {
|
||||||
ptr_int(self.context, self.ptr_bytes)
|
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> {
|
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:
|
// List of all supported LLVM intrinsics:
|
||||||
//
|
//
|
||||||
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-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 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(
|
add_intrinsic(
|
||||||
module,
|
module,
|
||||||
|
@ -158,8 +264,16 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
||||||
LLVM_CEILING_F64,
|
LLVM_CEILING_F64,
|
||||||
f64_type.fn_type(&[f64_type.into()], false),
|
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_SQRT_F64: &str = "llvm.sqrt.f64";
|
||||||
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
|
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
|
||||||
static LLVM_FABS_F64: &str = "llvm.fabs.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_COS_F64: &str = "llvm.cos.f64";
|
||||||
static LLVM_POW_F64: &str = "llvm.pow.f64";
|
static LLVM_POW_F64: &str = "llvm.pow.f64";
|
||||||
static LLVM_CEILING_F64: &str = "llvm.ceil.f64";
|
static LLVM_CEILING_F64: &str = "llvm.ceil.f64";
|
||||||
|
static LLVM_FLOOR_F64: &str = "llvm.floor.f64";
|
||||||
|
|
||||||
fn add_intrinsic<'ctx>(
|
fn add_intrinsic<'ctx>(
|
||||||
module: &Module<'ctx>,
|
module: &Module<'ctx>,
|
||||||
|
@ -521,21 +636,11 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
|
||||||
} else {
|
} else {
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
|
|
||||||
let len_u64 = str_literal.len() as u64;
|
let len_u64 = str_literal.len() as u64;
|
||||||
|
|
||||||
let elem_bytes = CHAR_LAYOUT.stack_size(env.ptr_bytes) 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| {
|
||||||
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!
|
|
||||||
};
|
|
||||||
|
|
||||||
// Copy the elements from the list literal into the array
|
// Copy the elements from the list literal into the array
|
||||||
for (index, char) in str_literal.as_bytes().iter().enumerate() {
|
for (index, char) in str_literal.as_bytes().iter().enumerate() {
|
||||||
let val = env
|
let val = env
|
||||||
|
@ -549,12 +654,75 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
builder.build_store(elem_ptr, val);
|
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 int_type = ptr_int(ctx, ptr_bytes);
|
||||||
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
|
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
|
||||||
let struct_type = collection(ctx, ptr_bytes);
|
let struct_type = collection(ctx, ptr_bytes);
|
||||||
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
|
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
|
||||||
|
|
||||||
let mut struct_val;
|
let mut struct_val;
|
||||||
|
|
||||||
// Store the pointer
|
// 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")
|
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Bitcast to an array of raw bytes
|
populate_str(ptr);
|
||||||
|
|
||||||
builder.build_bitcast(
|
builder.build_bitcast(
|
||||||
struct_val.into_struct_value(),
|
struct_val.into_struct_value(),
|
||||||
collection(ctx, ptr_bytes),
|
collection(ctx, ptr_bytes),
|
||||||
"cast_collection",
|
"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>(
|
pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
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>(
|
pub fn load_symbol_and_layout<'a, 'ctx, 'env, 'b>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &'b Scope<'a, 'ctx>,
|
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))
|
.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)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum InPlace {
|
pub enum InPlace {
|
||||||
InPlace,
|
InPlace,
|
||||||
|
@ -1813,13 +1962,23 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
// Str.concat : Str, Str -> Str
|
// Str.concat : Str, Str -> Str
|
||||||
debug_assert_eq!(args.len(), 2);
|
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);
|
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 => {
|
ListLen => {
|
||||||
// List.len : List * -> Int
|
// List.len : List * -> Int
|
||||||
|
@ -1948,7 +2107,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
list_join(env, inplace, parent, list, outer_list_layout)
|
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 => {
|
| NumToFloat => {
|
||||||
debug_assert_eq!(args.len(), 1);
|
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)
|
build_int_unary_op(env, arg.into_int_value(), arg_layout, op)
|
||||||
}
|
}
|
||||||
Float128 | Float64 | Float32 | Float16 => {
|
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);
|
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 => {
|
NumCompare => {
|
||||||
use inkwell::FloatPredicate;
|
use inkwell::FloatPredicate;
|
||||||
use inkwell::IntPredicate;
|
|
||||||
|
|
||||||
debug_assert_eq!(args.len(), 2);
|
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
|
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
|
||||||
| NumDivUnchecked | NumPow => {
|
| NumDivUnchecked | NumPow | NumPowInt => {
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
||||||
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
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>(
|
fn build_int_binop<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
lhs: IntValue<'ctx>,
|
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(),
|
NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(),
|
||||||
NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(),
|
NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(),
|
||||||
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_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);
|
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>(
|
fn build_float_binop<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
lhs: FloatValue<'ctx>,
|
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(),
|
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
|
||||||
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
|
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
|
||||||
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
|
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
|
||||||
NumPow => call_intrinsic(
|
NumPow => env.call_intrinsic(LLVM_POW_F64, &[lhs.into(), rhs.into()]),
|
||||||
LLVM_POW_F64,
|
|
||||||
env,
|
|
||||||
&[(lhs.into(), _lhs_layout), (rhs.into(), _rhs_layout)],
|
|
||||||
),
|
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Unrecognized int binary operation: {:?}", op);
|
unreachable!("Unrecognized int binary operation: {:?}", op);
|
||||||
}
|
}
|
||||||
|
@ -2540,22 +2541,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
NumToFloat => {
|
NumToFloat => {
|
||||||
// TODO specialize this to be not just for i64!
|
// TODO specialize this to be not just for i64!
|
||||||
let builtin_fn_name = "i64_to_f64_";
|
call_bitcode_fn(NumToFloat, env, &[arg.into()], "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))
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Unrecognized int unary operation: {:?}", op);
|
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>(
|
fn build_float_unary_op<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
arg: FloatValue<'ctx>,
|
arg: FloatValue<'ctx>,
|
||||||
arg_layout: &Layout<'a>,
|
|
||||||
op: LowLevel,
|
op: LowLevel,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
use roc_module::low_level::LowLevel::*;
|
use roc_module::low_level::LowLevel::*;
|
||||||
|
@ -2575,18 +2560,24 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
NumNeg => bd.build_float_neg(arg, "negate_float").into(),
|
NumNeg => bd.build_float_neg(arg, "negate_float").into(),
|
||||||
NumAbs => call_intrinsic(LLVM_FABS_F64, env, &[(arg.into(), arg_layout)]),
|
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
|
||||||
NumSqrtUnchecked => call_intrinsic(LLVM_SQRT_F64, env, &[(arg.into(), arg_layout)]),
|
NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]),
|
||||||
NumRound => call_intrinsic(LLVM_LROUND_I64_F64, env, &[(arg.into(), arg_layout)]),
|
NumRound => env.call_intrinsic(LLVM_LROUND_I64_F64, &[arg.into()]),
|
||||||
NumSin => call_intrinsic(LLVM_SIN_F64, env, &[(arg.into(), arg_layout)]),
|
NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]),
|
||||||
NumCos => call_intrinsic(LLVM_COS_F64, env, &[(arg.into(), arg_layout)]),
|
NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]),
|
||||||
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */
|
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */
|
||||||
NumCeiling => env.builder.build_cast(
|
NumCeiling => env.builder.build_cast(
|
||||||
InstructionOpcode::FPToSI,
|
InstructionOpcode::FPToSI,
|
||||||
call_intrinsic(LLVM_CEILING_F64, env, &[(arg.into(), arg_layout)]),
|
env.call_intrinsic(LLVM_CEILING_F64, &[arg.into()]),
|
||||||
env.context.i64_type(),
|
env.context.i64_type(),
|
||||||
"num_ceiling",
|
"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);
|
unreachable!("Unrecognized int unary operation: {:?}", op);
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,7 +236,7 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
// outer_list_len > 0
|
// outer_list_len > 0
|
||||||
// We do this check to avoid allocating memory. If the input
|
// We do this check to avoid allocating memory. If the input
|
||||||
// list is empty, then we can just return an empty list.
|
// 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 build_then = || {
|
||||||
let list_len_sum_name = "#listslengthsum";
|
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);
|
let inner_list_len = list_len(builder, inner_list_wrapper);
|
||||||
|
|
||||||
// inner_list_len > 0
|
// 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 =
|
let inner_list_non_empty_block =
|
||||||
ctx.append_basic_block(parent, "inner_list_non_empty");
|
ctx.append_basic_block(parent, "inner_list_non_empty");
|
||||||
|
@ -1125,14 +1125,13 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
// first_list_len > 0
|
// first_list_len > 0
|
||||||
// We do this check to avoid allocating memory. If the first input
|
// We do this check to avoid allocating memory. If the first input
|
||||||
// list is empty, then we can just return the second list cloned
|
// 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 = || {
|
let if_first_list_is_empty = || {
|
||||||
// second_list_len > 0
|
// second_list_len > 0
|
||||||
// We do this check to avoid allocating memory. If the second input
|
// We do this check to avoid allocating memory. If the second input
|
||||||
// list is empty, then we can just return an empty list
|
// list is empty, then we can just return an empty list
|
||||||
let second_list_length_comparison =
|
let second_list_length_comparison = list_is_not_empty(env, second_list_len);
|
||||||
list_is_not_empty(builder, ctx, second_list_len);
|
|
||||||
|
|
||||||
let build_second_list_then = || {
|
let build_second_list_then = || {
|
||||||
let elem_type =
|
let elem_type =
|
||||||
|
@ -1181,8 +1180,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
// second_list_len > 0
|
// second_list_len > 0
|
||||||
// We do this check to avoid allocating memory. If the second input
|
// We do this check to avoid allocating memory. If the second input
|
||||||
// list is empty, then we can just return the first list cloned
|
// list is empty, then we can just return the first list cloned
|
||||||
let second_list_length_comparison =
|
let second_list_length_comparison = list_is_not_empty(env, second_list_len);
|
||||||
list_is_not_empty(builder, ctx, second_list_len);
|
|
||||||
|
|
||||||
let if_second_list_is_not_empty = || {
|
let if_second_list_is_not_empty = || {
|
||||||
let combined_list_len =
|
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);
|
let first_list_ptr = load_list_ptr(builder, first_list_wrapper, ptr_type);
|
||||||
|
|
||||||
// FIRST LOOP
|
// FIRST LOOP
|
||||||
|
// TODO when the element type supports it, replace FIRST_LOOP with a memcpy!
|
||||||
let first_loop = |first_index, first_list_elem| {
|
let first_loop = |first_index, first_list_elem| {
|
||||||
// The pointer to the element in the combined list
|
// The pointer to the element in the combined list
|
||||||
let combined_list_elem_ptr = unsafe {
|
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);
|
let second_list_ptr = load_list_ptr(builder, second_list_wrapper, ptr_type);
|
||||||
|
|
||||||
// SECOND LOOP
|
// SECOND LOOP
|
||||||
|
// TODO when the element type supports it, replace SECOND_LOOP with a memcpy!
|
||||||
let second_loop = |second_index, second_list_elem| {
|
let second_loop = |second_index, second_list_elem| {
|
||||||
// The pointer to the element in the combined list.
|
// The pointer to the element in the combined list.
|
||||||
// Note that the pointer does not start at the index
|
// 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())
|
BasicValueEnum::StructValue(struct_type.const_zero())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_is_not_empty<'ctx>(
|
pub fn list_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> IntValue<'ctx> {
|
||||||
builder: &Builder<'ctx>,
|
env.builder.build_int_compare(
|
||||||
ctx: &'ctx Context,
|
|
||||||
list_len: IntValue<'ctx>,
|
|
||||||
) -> IntValue<'ctx> {
|
|
||||||
builder.build_int_compare(
|
|
||||||
IntPredicate::UGT,
|
IntPredicate::UGT,
|
||||||
list_len,
|
len,
|
||||||
ctx.i64_type().const_int(0, false),
|
env.ptr_int().const_zero(),
|
||||||
"greaterthanzero",
|
"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;
|
||||||
pub mod build_list;
|
pub mod build_list;
|
||||||
|
pub mod build_str;
|
||||||
pub mod compare;
|
pub mod compare;
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod refcounting;
|
pub mod refcounting;
|
||||||
|
|
|
@ -673,6 +673,16 @@ mod gen_num {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ceiling() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,6 @@ mod helpers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod gen_primitives {
|
mod gen_primitives {
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_str() {
|
|
||||||
assert_evals_to!("\"\"", "", &'static str);
|
|
||||||
assert_evals_to!("\"shirt and hat\"", "shirt and hat", &'static str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_int() {
|
fn basic_int() {
|
||||||
assert_evals_to!("123", 123, i64);
|
assert_evals_to!("123", 123, i64);
|
||||||
|
|
|
@ -14,11 +14,7 @@ mod helpers;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod gen_str {
|
mod gen_str {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_concat() {
|
fn str_concat_big_to_big() {
|
||||||
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);
|
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -31,4 +27,179 @@ mod gen_str {
|
||||||
&'static 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum LowLevel {
|
pub enum LowLevel {
|
||||||
StrConcat,
|
StrConcat,
|
||||||
|
StrIsEmpty,
|
||||||
ListLen,
|
ListLen,
|
||||||
ListGetUnsafe,
|
ListGetUnsafe,
|
||||||
ListSet,
|
ListSet,
|
||||||
|
@ -37,6 +38,8 @@ pub enum LowLevel {
|
||||||
NumToFloat,
|
NumToFloat,
|
||||||
NumPow,
|
NumPow,
|
||||||
NumCeiling,
|
NumCeiling,
|
||||||
|
NumPowInt,
|
||||||
|
NumFloor,
|
||||||
Eq,
|
Eq,
|
||||||
NotEq,
|
NotEq,
|
||||||
And,
|
And,
|
||||||
|
|
|
@ -642,6 +642,8 @@ define_builtins! {
|
||||||
37 NUM_COMPARE: "compare"
|
37 NUM_COMPARE: "compare"
|
||||||
38 NUM_POW: "pow"
|
38 NUM_POW: "pow"
|
||||||
39 NUM_CEILING: "ceiling"
|
39 NUM_CEILING: "ceiling"
|
||||||
|
40 NUM_POW_INT: "powInt"
|
||||||
|
41 NUM_FLOOR: "floor"
|
||||||
}
|
}
|
||||||
2 BOOL: "Bool" => {
|
2 BOOL: "Bool" => {
|
||||||
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
||||||
|
@ -655,7 +657,7 @@ define_builtins! {
|
||||||
3 STR: "Str" => {
|
3 STR: "Str" => {
|
||||||
0 STR_STR: "Str" imported // the Str.Str type alias
|
0 STR_STR: "Str" imported // the Str.Str type alias
|
||||||
1 STR_AT_STR: "@Str" // the Str.@Str private tag
|
1 STR_AT_STR: "@Str" // the Str.@Str private tag
|
||||||
2 STR_ISEMPTY: "isEmpty"
|
2 STR_IS_EMPTY: "isEmpty"
|
||||||
3 STR_APPEND: "append"
|
3 STR_APPEND: "append"
|
||||||
4 STR_CONCAT: "concat"
|
4 STR_CONCAT: "concat"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// - arguments that we may want to update destructively must be Owned
|
||||||
// - other refcounted arguments are Borrowed
|
// - other refcounted arguments are Borrowed
|
||||||
match op {
|
match op {
|
||||||
ListLen => arena.alloc_slice_copy(&[borrowed]),
|
ListLen | StrIsEmpty => arena.alloc_slice_copy(&[borrowed]),
|
||||||
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||||
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||||
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, 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]),
|
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
|
||||||
|
|
||||||
Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte
|
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])
|
arena.alloc_slice_copy(&[irrelevant, irrelevant])
|
||||||
}
|
}
|
||||||
|
|
||||||
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumToFloat
|
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
|
||||||
| NumCeiling | Not => arena.alloc_slice_copy(&[irrelevant]),
|
| NumToFloat | Not => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2382,6 +2382,18 @@ mod solve_expr {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pow() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Num.pow
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Float, Float -> Float",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ceiling() {
|
fn ceiling() {
|
||||||
infer_eq_without_problem(
|
infer_eq_without_problem(
|
||||||
|
@ -2395,14 +2407,26 @@ mod solve_expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pow() {
|
fn floor() {
|
||||||
infer_eq_without_problem(
|
infer_eq_without_problem(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
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",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
* [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
|
* [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
|
### 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
|
* [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
app Hello provides [ main ] imports []
|
app Hello provides [ main ] imports []
|
||||||
|
|
||||||
greeting =
|
greeting =
|
||||||
hi = "Hello, World!"
|
hi = "Hello, World!!!!!!!!!!!!!"
|
||||||
|
|
||||||
hi
|
hi
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,9 @@ let
|
||||||
in { pkgs ? pinnedPkgs }:
|
in { pkgs ? pinnedPkgs }:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
isOsX = builtins.currentSystem == "x86_64-darwin";
|
isOsX = builtins.currentSystem == "x86_64-darwin";
|
||||||
darwin-frameworks = if isOsX then
|
darwin-frameworks =
|
||||||
|
if isOsX then
|
||||||
with pkgs.darwin.apple_sdk.frameworks; [
|
with pkgs.darwin.apple_sdk.frameworks; [
|
||||||
AppKit
|
AppKit
|
||||||
CoreFoundation
|
CoreFoundation
|
||||||
|
@ -30,7 +30,7 @@ let
|
||||||
[
|
[
|
||||||
pkgs.rustup
|
pkgs.rustup
|
||||||
pkgs.cargo
|
pkgs.cargo
|
||||||
pkgs.llvm_10
|
llvm
|
||||||
# libraries for llvm
|
# libraries for llvm
|
||||||
pkgs.libffi
|
pkgs.libffi
|
||||||
pkgs.libxml2
|
pkgs.libxml2
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue