mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Merge remote-tracking branch 'origin/trunk' into small-str
This commit is contained in:
commit
5d3645350d
33 changed files with 2597 additions and 902 deletions
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
@ -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");
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ pub enum LowLevel {
|
|||
NumSqrtUnchecked,
|
||||
NumRound,
|
||||
NumToFloat,
|
||||
NumPow,
|
||||
NumCeiling,
|
||||
Eq,
|
||||
NotEq,
|
||||
And,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue