Merge remote-tracking branch 'origin/trunk' into small-str

This commit is contained in:
Richard Feldman 2020-09-16 02:23:06 -04:00
commit 5d3645350d
33 changed files with 2597 additions and 902 deletions

View file

@ -6,7 +6,7 @@ Builtins are the functions and modules that are implicitly imported into every m
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Some of these have `#` inside their name (`first#list`, #lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Int -> elem` in LLVM
@ -15,9 +15,9 @@ But we can use these values and some of these are necessary for implementing bui
### can/src/builtins.rs
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`, which is complex the reasons mentioned in the previous section; `list_get` returns tags, and it defers to lower-level functions via an if statement.
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`. What makes `list_get` is that it returns tags, and in order to return tags it first has to defer to lower-level functions via an if statement.
Lets look at `List.repeat : elem, Int -> List elem`, which is more straight forward, and points directly to its lower level implementation:
Lets look at `List.repeat : elem, Int -> List elem`, which is more straight-forward, and points directly to its lower level implementation:
```
fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let elem_var = var_store.fresh();
@ -48,21 +48,25 @@ Since `List.repeat` is implemented entirely as low level functions, its `body` i
## Connecting the definition to the implementation
### module/src/low_level.rs
This `LowLevel` thing connects the builtin defined in this module to its implementation. Its referenced in `can/src/builtins.rs` and it is used in `gen/src/llvm/build.rs`.
This `LowLevel` thing connects the builtin defined in this module to its implementation. Its referenced in `can/src/builtins.rs` and it is used in `gen/src/llvm/build.rs`.
## Bottom level LLVM values and functions
### gen/src/llvm/build.rs
This is where bottom-level functions that need to be written as LLVM are created. If the function leads to a tag thats a good sign it should not be written here in `build.rs`. If its simple fundamental stuff like `INT_ADD` then it certainly should be written here.
## Letting the compiler know these functions exist
Its one thing to actually write these functions, its _another_ thing to let the Roc compiler know they exist as part of the standard library. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in these modules:
### builtins/src/std.rs
### builtins/src/unique.rs
Its one thing to actually write these functions, its _another_ thing to let the Roc compiler know they exist as part of the standard library. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in `std.rs`.
## Specifying the uniqueness of a function
### builtins/src/unique.rs
One of the cool things about Roc is that it evaluates if a value in memory is shared between scopes or if it is used in just one place. If the value is used in one place then it is 'unique', and it therefore can be mutated in place. For a value created by a function, the uniqueness of the output is determined in part by the uniqueness of the input arguments. For example `List.single : elem -> List elem` can return a unique list if the `elem` is also unique.
We have to define the uniqueness constraints of a function just like we have to define a type signature. That is what happens in `unique.rs`. This can be tricky so it would be a good step to ask for help on if it is confusing.
# Mistakes that are easy to make!!
When implementing a new builtin, it is often easy to copy and paste the implementation for an existing builtin. This can take you quite far since many builtins are very similar, but it also risks forgetting to change one small part of what you copy and pasted and losing a lot of time later on when you cant figure out why things dont work. So, speaking from experience, even if you are copying an existing builtin, try and implement it manually without copying and pasting. Two recent instances of this (as of September 7th, 2020):
- `List.keepIf` did not work for a long time because in builtins its `LowLevel` was `ListMap`. This was because I copy and pasted the `List.map` implementation in `builtins.rs
- `List.walkRight` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keepIf`.
- `List.walkRight` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keepIf`.

View file

@ -282,7 +282,11 @@ joinMap : List before, (before -> List after) -> List after
## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ]
## >>> |> List.map List.first
## >>> |> List.joinOks
joinOks : List (Result elem *) -> List elem
##
## Eventually, `oks` type signature will be `List [Ok elem]* -> List elem`.
## The implementation for that is a lot tricker then `List (Result elem *)`
## so we're sticking with `Result` for now.
oks : List (Result elem *) -> List elem
## Iterates over the shortest of the given lists and returns a list of `Pair`
## tags, each wrapping one of the elements in that list, along with the elements

View file

@ -412,6 +412,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// minFloat : Float
add_type(Symbol::NUM_MIN_FLOAT, float_type());
// pow : Float, Float -> Float
add_type(
Symbol::NUM_POW,
SolvedType::Func(vec![float_type(), float_type()], Box::new(float_type())),
);
// ceiling : Float -> Int
add_type(
Symbol::NUM_CEILING,
SolvedType::Func(vec![float_type()], Box::new(int_type())),
);
// Bool module
// and : Bool, Bool -> Bool

View file

@ -448,6 +448,21 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![num_type(star1, a)], bool_type(star2))
});
// pow : Float, Float -> Float
add_type(Symbol::NUM_POW, {
let_tvars! { star1, star2, star3 };
unique_function(
vec![float_type(star1), float_type(star2)],
float_type(star3),
)
});
// ceiling : Float -> Int
add_type(Symbol::NUM_CEILING, {
let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], int_type(star2))
});
// Bool module
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool

View file

@ -91,6 +91,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_IS_POSITIVE => num_is_positive,
Symbol::NUM_IS_NEGATIVE => num_is_negative,
Symbol::NUM_TO_FLOAT => num_to_float,
Symbol::NUM_POW => num_pow,
Symbol::NUM_CEILING => num_ceiling,
}
}
@ -579,6 +581,47 @@ fn num_round(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.pow : Float, Float -> Float
fn num_pow(symbol: Symbol, var_store: &mut VarStore) -> Def {
let float_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumPow,
args: vec![
(float_var, Var(Symbol::ARG_1)),
(float_var, Var(Symbol::ARG_2)),
],
ret_var: float_var,
};
defn(
symbol,
vec![(float_var, Symbol::ARG_1), (float_var, Symbol::ARG_2)],
var_store,
body,
float_var,
)
}
/// Num.ceiling : Float -> Int
fn num_ceiling(symbol: Symbol, var_store: &mut VarStore) -> Def {
let float_var = var_store.fresh();
let int_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumCeiling,
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();

View file

@ -24,7 +24,8 @@ use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{BasicTypeEnum, FunctionType, IntType, StructType};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{
BasicValue, CallSiteValue, FloatValue, FunctionValue, IntValue, PointerValue, StructValue,
BasicValue, CallSiteValue, FloatValue, FunctionValue, InstructionOpcode, IntValue,
PointerValue, StructValue,
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
@ -194,6 +195,34 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
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,
LLVM_SQRT_F64,
@ -226,30 +255,14 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
add_intrinsic(
module,
LLVM_MEMSET_I64,
void_type.fn_type(
&[
i8_ptr_type.into(),
i8_type.into(),
i64_type.into(),
i1_type.into(),
],
false,
),
LLVM_POW_F64,
f64_type.fn_type(&[f64_type.into(), f64_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,
),
LLVM_CEILING_F64,
f64_type.fn_type(&[f64_type.into()], false),
);
}
@ -260,6 +273,8 @@ static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
static LLVM_FABS_F64: &str = "llvm.fabs.f64";
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";
fn add_intrinsic<'ctx>(
module: &Module<'ctx>,
@ -320,6 +335,133 @@ pub fn construct_optimization_passes<'a>(
(mpm, fpm)
}
/// For communication with C (tests and platforms) we need to abide by the C calling convention
///
/// While small values are just returned like with the fast CC, larger structures need to
/// be written into a pointer (into the callers stack)
enum PassVia {
Register,
Memory,
}
impl PassVia {
fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self {
if layout.stack_size(ptr_bytes) > 16 {
PassVia::Memory
} else {
PassVia::Register
}
}
}
pub fn make_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
use inkwell::types::BasicType;
use PassVia::*;
let context = env.context;
let builder = env.builder;
let arena = env.arena;
let ptr_bytes = env.ptr_bytes;
let return_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes);
let roc_main_fn_name = "$Test.roc_main";
// make the roc main function
let roc_main_fn_type = return_type.fn_type(&[], false);
// Add main to the module.
let roc_main_fn = env
.module
.add_function(roc_main_fn_name, roc_main_fn_type, None);
// our exposed main function adheres to the C calling convention
roc_main_fn.set_call_conventions(FAST_CALL_CONV);
// Add main's body
let basic_block = context.append_basic_block(roc_main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
build_exp_stmt(
env,
layout_ids,
&mut Scope::default(),
roc_main_fn,
main_body,
);
// build the C calling convention wrapper
let main_fn_name = "$Test.main";
let register_or_memory = PassVia::from_layout(env.ptr_bytes, layout);
let main_fn_type = match register_or_memory {
Memory => {
let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into();
context.void_type().fn_type(&[return_value_ptr], false)
}
Register => return_type.fn_type(&[], false),
};
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
// our exposed main function adheres to the C calling convention
main_fn.set_call_conventions(C_CALL_CONV);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
let call = builder.build_call(roc_main_fn, &[], "call_roc_main");
call.set_call_convention(FAST_CALL_CONV);
let call_result = call.try_as_basic_value().left().unwrap();
match register_or_memory {
Memory => {
// write the result into the supplied pointer
// this is a void function, therefore return None
let ptr_return_type = return_type.ptr_type(AddressSpace::Generic);
let ptr_as_int = main_fn.get_first_param().unwrap();
let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr");
builder.build_store(ptr.into_pointer_value(), call_result);
builder.build_return(None);
}
Register => {
// construct a normal return
// values are passed to the caller via registers
builder.build_return(Some(&call_result));
}
}
(main_fn_name, env.arena.alloc(main_fn))
}
fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace {
match layout {
Layout::Builtin(Builtin::EmptyList) => InPlace::InPlace,
Layout::Builtin(Builtin::List(memory_mode, _)) => match memory_mode {
MemoryMode::Unique => InPlace::InPlace,
MemoryMode::Refcounted => InPlace::Clone,
},
Layout::Builtin(Builtin::EmptyStr) => InPlace::InPlace,
Layout::Builtin(Builtin::Str) => InPlace::Clone,
_ => unreachable!("Layout {:?} does not have an inplace", layout),
}
}
pub fn build_exp_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
literal: &roc_mono::ir::Literal<'a>,
@ -418,8 +560,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
let ptr = allocate_list(env, &CHAR_LAYOUT, len);
let ptr = allocate_list(env, InPlace::Clone, &CHAR_LAYOUT, len);
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, ptr_bytes);
@ -461,6 +602,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
layout: &Layout<'a>,
expr: &roc_mono::ir::Expr<'a>,
) -> BasicValueEnum<'ctx> {
use roc_mono::ir::CallType::*;
@ -468,7 +610,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
match expr {
Literal(literal) => build_exp_literal(env, literal),
RunLowLevel(op, symbols) => run_low_level(env, scope, parent, *op, symbols),
RunLowLevel(op, symbols) => run_low_level(env, scope, parent, layout, *op, symbols),
FunctionCall {
call_type: ByName(name),
@ -833,7 +975,11 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}
}
EmptyArray => empty_polymorphic_list(env),
Array { elem_layout, elems } => list_literal(env, scope, elem_layout, elems),
Array { elem_layout, elems } => {
let inplace = get_inplace_from_layout(layout);
list_literal(env, inplace, scope, elem_layout, elems)
}
FunctionPointer(symbol, layout) => {
let fn_name = layout_ids
.get(*symbol, layout)
@ -922,6 +1068,7 @@ pub fn allocate_with_refcount<'a, 'ctx, 'env>(
fn list_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
scope: &Scope<'a, 'ctx>,
elem_layout: &Layout<'a>,
elems: &&[Symbol],
@ -937,7 +1084,7 @@ fn list_literal<'a, 'ctx, 'env>(
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
allocate_list(env, elem_layout, len)
allocate_list(env, inplace, elem_layout, len)
// TODO check if malloc returned null; if so, runtime error for OOM!
};
@ -995,7 +1142,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
Let(symbol, expr, layout, cont) => {
let context = &env.context;
let val = build_exp_expr(env, layout_ids, &scope, parent, &expr);
let val = build_exp_expr(env, layout_ids, &scope, parent, layout, &expr);
let expr_bt = if let Layout::RecursivePointer = layout {
match expr {
Expr::AccessAtIndex { field_layouts, .. } => {
@ -1611,6 +1758,7 @@ fn call_with_args<'a, 'ctx, 'env>(
.unwrap_or_else(|| panic!("LLVM error: Invalid call by name for name {:?}", symbol))
}
#[derive(Copy, Clone)]
pub enum InPlace {
InPlace,
Clone,
@ -1639,6 +1787,7 @@ fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
layout: &Layout<'a>,
op: LowLevel,
args: &[Symbol],
) -> BasicValueEnum<'ctx> {
@ -1649,7 +1798,9 @@ fn run_low_level<'a, 'ctx, 'env>(
// Str.concat : Str, Str -> Str
debug_assert_eq!(args.len(), 2);
str_concat(env, scope, parent, args[0], args[1])
let inplace = get_inplace_from_layout(layout);
str_concat(env, inplace, scope, parent, args[0], args[1])
}
StrIsEmpty => {
// Str.isEmpty : Str -> Str
@ -1679,7 +1830,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]);
list_single(env, arg, arg_layout)
let inplace = get_inplace_from_layout(layout);
list_single(env, inplace, arg, arg_layout)
}
ListRepeat => {
// List.repeat : Int, elem -> List elem
@ -1688,7 +1841,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let list_len = load_symbol(env, scope, &args[0]).into_int_value();
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_repeat(env, parent, list_len, elem, elem_layout)
let inplace = get_inplace_from_layout(layout);
list_repeat(env, inplace, parent, list_len, elem, elem_layout)
}
ListReverse => {
// List.reverse : List elem -> List elem
@ -1696,7 +1851,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
list_reverse(env, parent, list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_reverse(env, parent, inplace, list, list_layout)
}
ListConcat => {
debug_assert_eq!(args.len(), 2);
@ -1705,7 +1862,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let second_list = load_symbol(env, scope, &args[1]);
list_concat(env, parent, first_list, second_list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_concat(env, inplace, parent, first_list, second_list, list_layout)
}
ListMap => {
// List.map : List before, (before -> after) -> List after
@ -1715,7 +1874,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_map(env, parent, func, func_layout, list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_map(env, inplace, parent, func, func_layout, list, list_layout)
}
ListKeepIf => {
// List.keepIf : List elem, (elem -> Bool) -> List elem
@ -1725,7 +1886,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_keep_if(env, parent, func, func_layout, list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_keep_if(env, inplace, parent, func, func_layout, list, list_layout)
}
ListWalkRight => {
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
@ -1755,7 +1918,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let original_wrapper = load_symbol(env, scope, &args[0]).into_struct_value();
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_append(env, original_wrapper, elem, elem_layout)
let inplace = get_inplace_from_layout(layout);
list_append(env, inplace, original_wrapper, elem, elem_layout)
}
ListPrepend => {
// List.prepend : List elem, elem -> List elem
@ -1764,7 +1929,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let original_wrapper = load_symbol(env, scope, &args[0]).into_struct_value();
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_prepend(env, original_wrapper, elem, elem_layout)
let inplace = get_inplace_from_layout(layout);
list_prepend(env, inplace, original_wrapper, elem, elem_layout)
}
ListJoin => {
// List.join : List (List elem) -> List elem
@ -1772,9 +1939,12 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, outer_list_layout) = load_symbol_and_layout(env, scope, &args[0]);
list_join(env, parent, list, outer_list_layout)
let inplace = get_inplace_from_layout(layout);
list_join(env, inplace, parent, list, outer_list_layout)
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => {
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling
| NumToFloat => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -1885,7 +2055,7 @@ fn run_low_level<'a, 'ctx, 'env>(
}
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumDivUnchecked => {
| NumDivUnchecked | NumPow => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -1990,6 +2160,8 @@ fn run_low_level<'a, 'ctx, 'env>(
ListSetInPlace => {
let (list_symbol, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
let output_inplace = get_inplace_from_layout(layout);
list_set(
parent,
&[
@ -1999,6 +2171,7 @@ fn run_low_level<'a, 'ctx, 'env>(
],
env,
InPlace::InPlace,
output_inplace,
)
}
ListSet => {
@ -2010,49 +2183,70 @@ fn run_low_level<'a, 'ctx, 'env>(
(load_symbol_and_layout(env, scope, &args[2])),
];
match list_layout {
Layout::Builtin(Builtin::List(MemoryMode::Unique, _)) => {
// the layout tells us this List.set can be done in-place
list_set(parent, arguments, env, InPlace::InPlace)
}
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => {
// no static guarantees, but all is not lost: we can check the refcount
// if it is one, we hold the final reference, and can mutate it in-place!
let builder = env.builder;
let ctx = env.context;
let output_inplace = get_inplace_from_layout(layout);
let ret_type =
basic_type_from_layout(env.arena, ctx, list_layout, env.ptr_bytes);
let in_place = || list_set(parent, arguments, env, InPlace::InPlace, output_inplace);
let clone = || list_set(parent, arguments, env, InPlace::Clone, output_inplace);
let empty = || list_symbol;
let refcount_ptr =
list_get_refcount_ptr(env, list_layout, list_symbol.into_struct_value());
let refcount = env
.builder
.build_load(refcount_ptr, "get_refcount")
.into_int_value();
let comparison = refcount_is_one_comparison(builder, env.context, refcount);
// build then block
// refcount is 1, so work in-place
let build_pass = || list_set(parent, arguments, env, InPlace::InPlace);
// build else block
// refcount != 1, so clone first
let build_fail = || list_set(parent, arguments, env, InPlace::Clone);
crate::llvm::build_list::build_basic_phi2(
env, parent, comparison, build_pass, build_fail, ret_type,
)
}
Layout::Builtin(Builtin::EmptyList) => list_symbol,
other => unreachable!("List.set: weird layout {:?}", other),
}
maybe_inplace_list(
env,
parent,
list_layout,
list_symbol.into_struct_value(),
in_place,
clone,
empty,
)
}
}
}
fn maybe_inplace_list<'a, 'ctx, 'env, InPlace, CloneFirst, Empty>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
list_layout: &Layout<'a>,
original_wrapper: StructValue<'ctx>,
mut in_place: InPlace,
clone: CloneFirst,
mut empty: Empty,
) -> BasicValueEnum<'ctx>
where
InPlace: FnMut() -> BasicValueEnum<'ctx>,
CloneFirst: FnMut() -> BasicValueEnum<'ctx>,
Empty: FnMut() -> BasicValueEnum<'ctx>,
{
match list_layout {
Layout::Builtin(Builtin::List(MemoryMode::Unique, _)) => {
// the layout tells us this List.set can be done in-place
in_place()
}
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => {
// no static guarantees, but all is not lost: we can check the refcount
// if it is one, we hold the final reference, and can mutate it in-place!
let builder = env.builder;
let ctx = env.context;
let ret_type = basic_type_from_layout(env.arena, ctx, list_layout, env.ptr_bytes);
let refcount_ptr = list_get_refcount_ptr(env, list_layout, original_wrapper);
let refcount = env
.builder
.build_load(refcount_ptr, "get_refcount")
.into_int_value();
let comparison = refcount_is_one_comparison(builder, env.context, refcount);
crate::llvm::build_list::build_basic_phi2(
env, parent, comparison, in_place, clone, ret_type,
)
}
Layout::Builtin(Builtin::EmptyList) => empty(),
other => unreachable!("Attempting list operation on invalid layout {:?}", other),
}
}
fn build_int_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
lhs: IntValue<'ctx>,
@ -2105,6 +2299,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 => env.call_intrinsic(LLVM_POW_F64, &[lhs.into(), rhs.into()]),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);
}
@ -2202,6 +2397,12 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
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,
env.call_intrinsic(LLVM_CEILING_F64, &[arg.into()]),
env.context.i64_type(),
"num_ceiling",
),
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
use crate::llvm::build::{ptr_from_symbol, Env, Scope};
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,
LoopListArg,
};
use crate::llvm::convert::{collection, ptr_int};
use inkwell::builder::Builder;
@ -16,6 +15,7 @@ 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,
@ -55,6 +55,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
let if_second_str_is_nonempty = || {
let (new_wrapper, _) = clone_nonempty_str(
env,
inplace,
second_str_smallness,
second_str_len,
second_str_ptr,
@ -79,6 +80,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
let if_second_str_is_empty = || {
let (new_wrapper, _) = clone_nonempty_str(
env,
inplace,
first_str_smallness,
first_str_len,
first_str_ptr,
@ -111,7 +113,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
let if_big = || {
let combined_str_ptr =
allocate_list(env, &CHAR_LAYOUT, combined_str_len);
allocate_list(env, inplace, &CHAR_LAYOUT, combined_str_len);
// TODO replace FIRST_LOOP with a memcpy!
// FIRST LOOP
@ -133,14 +135,11 @@ pub fn str_concat<'a, 'ctx, 'env>(
let index_alloca = incrementing_elem_loop(
builder,
parent,
ctx,
LoopListArg {
ptr: first_str_ptr,
len: first_str_len,
},
parent,
first_str_ptr,
first_str_len,
index_name,
None,
first_loop,
);
@ -179,14 +178,11 @@ pub fn str_concat<'a, 'ctx, 'env>(
incrementing_elem_loop(
builder,
parent,
ctx,
LoopListArg {
ptr: second_str_ptr,
len: second_str_len,
},
parent,
second_str_ptr,
second_str_len,
index_name,
Some(index_alloca),
second_loop,
);
@ -220,14 +216,11 @@ pub fn str_concat<'a, 'ctx, 'env>(
let index_alloca = incrementing_elem_loop(
builder,
parent,
ctx,
LoopListArg {
ptr: first_str_ptr,
len: first_str_len,
},
parent,
first_str_ptr,
first_str_len,
index_name,
None,
first_loop,
);
@ -266,14 +259,11 @@ pub fn str_concat<'a, 'ctx, 'env>(
incrementing_elem_loop(
builder,
parent,
ctx,
LoopListArg {
ptr: second_str_ptr,
len: second_str_len,
},
parent,
second_str_ptr,
second_str_len,
index_name,
Some(index_alloca),
second_loop,
);
@ -445,6 +435,7 @@ enum Smallness {
fn clone_nonempty_str<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
smallness: Smallness,
len: IntValue<'ctx>,
bytes_ptr: PointerValue<'ctx>,
@ -465,7 +456,7 @@ fn clone_nonempty_str<'a, 'ctx, 'env>(
(wrapper_struct.into_struct_value(), alloca)
}
Smallness::Big => {
let clone_ptr = allocate_list(env, &CHAR_LAYOUT, len);
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");

View file

@ -665,4 +665,14 @@ mod gen_num {
i64
);
}
#[test]
fn pow() {
assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
}
#[test]
fn ceiling() {
assert_evals_to!("Num.ceiling 1.5", 2, i64);
}
}

View file

@ -533,4 +533,147 @@ mod gen_records {
i64
);
}
#[test]
fn return_record_2() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
[3, 5],
[i64; 2]
);
}
#[test]
fn return_record_3() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 4 }
"#
),
(3, 5, 4),
(i64, i64, i64)
);
}
#[test]
fn return_record_4() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2 }
"#
),
[3, 5, 4, 2],
[i64; 4]
);
}
#[test]
fn return_record_5() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1 }
"#
),
[3, 5, 4, 2, 1],
[i64; 5]
);
}
#[test]
fn return_record_6() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
"#
),
[3, 5, 4, 2, 1, 7],
[i64; 6]
);
}
#[test]
fn return_record_7() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
"#
),
[3, 5, 4, 2, 1, 7, 8],
[i64; 7]
);
}
#[test]
fn return_record_float_int() {
assert_evals_to!(
indoc!(
r#"
{ a: 3.14, b: 0x1 }
"#
),
(3.14, 0x1),
(f64, i64)
);
}
#[test]
fn return_record_int_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 0x1, b: 3.14 }
"#
),
(0x1, 3.14),
(i64, f64)
);
}
#[test]
fn return_record_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14 }
"#
),
(6.28, 3.14),
(f64, f64)
);
}
#[test]
fn return_record_float_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14, c: 0.1 }
"#
),
(6.28, 3.14, 0.1),
(f64, f64, f64)
);
}
#[test]
fn just_to_be_sure() {
assert_evals_to!(
indoc!(
r#"
{ a: 1, b : 2, c : 3 }
"#
),
[1, 2, 3],
[i64; 3]
);
}
}

View file

@ -8,11 +8,8 @@ pub fn helper_without_uniqueness<'a>(
context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::Scope;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout;
let target = target_lexicon::Triple::host();
@ -66,7 +63,7 @@ pub fn helper_without_uniqueness<'a>(
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}",
err
@ -76,10 +73,6 @@ pub fn helper_without_uniqueness<'a>(
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let main_fn_type =
basic_type_from_layout(&arena, context, &layout, ptr_bytes).fn_type(&[], false);
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
@ -163,25 +156,8 @@ pub fn helper_without_uniqueness<'a>(
}
}
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
let cc = roc_gen::llvm::build::FAST_CALL_CONV;
main_fn.set_call_conventions(cc);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&mut Scope::default(),
main_fn,
&main_body,
);
let (main_fn_name, main_fn) =
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -212,11 +188,8 @@ pub fn helper_with_uniqueness<'a>(
context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{infer_expr, uniq_expr};
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::Scope;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout;
let target = target_lexicon::Triple::host();
@ -258,7 +231,7 @@ pub fn helper_with_uniqueness<'a>(
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}",
err
@ -269,11 +242,6 @@ pub fn helper_with_uniqueness<'a>(
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let main_fn_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes)
.fn_type(&[], false)
.clone();
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
@ -357,25 +325,8 @@ pub fn helper_with_uniqueness<'a>(
}
}
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
let cc = roc_gen::llvm::build::FAST_CALL_CONV;
main_fn.set_call_conventions(cc);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&mut Scope::default(),
main_fn,
&main_body,
);
let (main_fn_name, main_fn) =
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
// you're in the version with uniqueness!

View file

@ -36,6 +36,8 @@ pub enum LowLevel {
NumSqrtUnchecked,
NumRound,
NumToFloat,
NumPow,
NumCeiling,
Eq,
NotEq,
And,

View file

@ -640,6 +640,8 @@ define_builtins! {
35 NUM_SQRT: "sqrt"
36 NUM_ROUND: "round"
37 NUM_COMPARE: "compare"
38 NUM_POW: "pow"
39 NUM_CEILING: "ceiling"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -522,12 +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 => {
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow => {
arena.alloc_slice_copy(&[irrelevant, irrelevant])
}
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumToFloat | Not => {
arena.alloc_slice_copy(&[irrelevant])
}
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumToFloat
| NumCeiling | Not => arena.alloc_slice_copy(&[irrelevant]),
}
}

View file

@ -86,26 +86,33 @@ where
return err_unexpected();
}
}
next_ch if next_ch.is_ascii_digit() => {
has_parsed_digits = true;
'_' => {
// Underscores are ignored.
}
next_ch
if next_ch != '_' &&
// ASCII alphabetic chars (like 'a' and 'f') are allowed in Hex int literals.
// We parse them in any int literal, so we can give a more helpful error
// in canonicalization (e.g. "the character 'f' is not allowed in Octal literals"
// or "the character 'g' is outside the range of valid Hex literals")
!next_ch.is_ascii_alphabetic() =>
{
if has_parsed_digits {
// We hit an invalid number literal character; we're done!
break;
next_ch => {
if next_ch.is_ascii_digit() {
has_parsed_digits = true;
} else {
// No digits! We likely parsed a minus sign that's actually an operator.
return err_unexpected();
if !has_parsed_digits {
// No digits! We likely parsed a minus sign
// that's actually a unary negation operator.
return err_unexpected();
}
// ASCII alphabetic chars (like 'a' and 'f') are
// allowed in Hex int literals. We verify them in
// canonicalization, so if there's a problem, we can
// give a more helpful error (e.g. "the character 'f'
// is not allowed in Octal literals" or
// "the character 'g' is outside the range of valid
// Hex literals") while still allowing the formatter
// to format them normally.
if !next_ch.is_ascii_alphabetic() {
// We hit an invalid number literal character; we're done!
break;
}
}
}
_ => {}
}
// Since we only consume characters in the ASCII range for number literals,

View file

@ -1405,6 +1405,23 @@ mod test_parse {
assert_eq!(Ok(expected), actual);
}
#[test]
fn unary_negation_access() {
// Regression test for https://github.com/rtfeldman/roc/issues/509
let arena = Bump::new();
let var = Var {
module_name: "",
ident: "rec1",
};
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
let access = Access(arena.alloc(var), "field");
let loc_access = Located::new(0, 0, 1, 11, access);
let expected = UnaryOp(arena.alloc(loc_access), loc_op);
let actual = parse_with(&arena, "-rec1.field");
assert_eq!(Ok(expected), actual);
}
// CLOSURE
#[test]

View file

@ -2382,6 +2382,30 @@ mod solve_expr {
);
}
#[test]
fn ceiling() {
infer_eq_without_problem(
indoc!(
r#"
Num.ceiling
"#
),
"Float -> Int",
);
}
#[test]
fn pow() {
infer_eq_without_problem(
indoc!(
r#"
Num.pow
"#
),
"Float, Float -> Float",
);
}
#[test]
fn reconstruct_path() {
infer_eq_without_problem(