mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 22:09:09 +00:00
Merge remote-tracking branch 'origin/trunk' into divTruncate
This commit is contained in:
commit
bd98ac6dc7
33 changed files with 2455 additions and 1155 deletions
8
AUTHORS
8
AUTHORS
|
@ -28,3 +28,11 @@ Ju Liu <ju@noredink.com>
|
|||
Peter Fields <pcfields@gmail.com>
|
||||
Brian J. Cardiff <bcardiff@gmail.com>
|
||||
Basile Henry <bjm.henry@gmail.com>
|
||||
Tarjei Skjærset <tskj@tarjei.org>
|
||||
Brian Hicks <brian@brianthicks.com>
|
||||
Dan Gieschen Knutson <dan.knutson@gmail.com>
|
||||
Joshua Hoeflich <joshuaharry411@icloud.com>
|
||||
Brian Carroll <brian.carroll.ireland@gmail.com>
|
||||
Kofi Gumbs <h.kofigumbs@gmail.com>
|
||||
Luiz de Oliveira <luizcarlos1405@gmail.com>
|
||||
Chelsea Troy <chelsea.dommert@gmail.com>
|
||||
|
|
|
@ -116,6 +116,11 @@ mod repl_eval {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_ceil_division_success() {
|
||||
expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_in_record() {
|
||||
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
|
||||
|
|
|
@ -83,6 +83,7 @@ const FLOATS = [_]type{ f32, f64 };
|
|||
const NUMBERS = INTEGERS ++ FLOATS;
|
||||
|
||||
comptime {
|
||||
exportNumFn(num.divCeil, "div_ceil");
|
||||
exportNumFn(num.bytesToU16C, "bytes_to_u16");
|
||||
exportNumFn(num.bytesToU32C, "bytes_to_u32");
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ pub fn exportRound(comptime T: type, comptime name: []const u8) void {
|
|||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn divCeil(numerator: i64, denominator: i64) callconv(.C) i64 {
|
||||
return @call(.{ .modifier = always_inline }, math.divCeil, .{ i64, numerator, denominator }) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
|
||||
return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position });
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ pub const NUM_ACOS: &str = "roc_builtins.num.acos";
|
|||
pub const NUM_ATAN: &str = "roc_builtins.num.atan";
|
||||
pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite";
|
||||
pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int";
|
||||
pub const NUM_DIV_CEIL: &str = "roc_builtins.num.div_ceil";
|
||||
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
|
||||
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
|
||||
pub const NUM_ROUND: &str = "roc_builtins.num.round";
|
||||
|
|
|
@ -293,18 +293,25 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
// minInt : Int range
|
||||
add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1)));
|
||||
|
||||
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
let div_by_zero = SolvedType::TagUnion(
|
||||
vec![(TagName::Global("DivByZero".into()), vec![])],
|
||||
Box::new(SolvedType::Wildcard),
|
||||
);
|
||||
|
||||
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_DIV_INT,
|
||||
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
|
||||
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
|
||||
);
|
||||
|
||||
//divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_DIV_CEIL,
|
||||
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
|
||||
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
|
||||
);
|
||||
|
||||
// bitwiseAnd : Int a, Int a -> Int a
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_BITWISE_AND,
|
||||
|
|
|
@ -143,6 +143,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
NUM_TAN => num_tan,
|
||||
NUM_DIV_FLOAT => num_div_float,
|
||||
NUM_DIV_INT => num_div_int,
|
||||
NUM_DIV_CEIL => num_div_ceil,
|
||||
NUM_ABS => num_abs,
|
||||
NUM_NEG => num_neg,
|
||||
NUM_REM => num_rem,
|
||||
|
@ -2844,6 +2845,72 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
/// Num.divCeil : Int a , Int a -> Result (Int a) [ DivByZero ]*
|
||||
fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var = var_store.fresh();
|
||||
let unbound_zero_var = var_store.fresh();
|
||||
let unbound_zero_precision_var = var_store.fresh();
|
||||
let ret_var = var_store.fresh();
|
||||
|
||||
let body = If {
|
||||
branch_var: ret_var,
|
||||
cond_var: bool_var,
|
||||
branches: vec![(
|
||||
// if-condition
|
||||
no_region(
|
||||
// Num.neq denominator 0
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(num_var, Var(Symbol::ARG_2)),
|
||||
(
|
||||
num_var,
|
||||
int(unbound_zero_var, unbound_zero_precision_var, 0),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
},
|
||||
),
|
||||
// denominator was not zero
|
||||
no_region(
|
||||
// Ok (Int.#divUnchecked numerator denominator)
|
||||
tag(
|
||||
"Ok",
|
||||
vec![
|
||||
// Num.#divUnchecked numerator denominator
|
||||
RunLowLevel {
|
||||
op: LowLevel::NumDivCeilUnchecked,
|
||||
args: vec![
|
||||
(num_var, Var(Symbol::ARG_1)),
|
||||
(num_var, Var(Symbol::ARG_2)),
|
||||
],
|
||||
ret_var: num_var,
|
||||
},
|
||||
],
|
||||
var_store,
|
||||
),
|
||||
),
|
||||
)],
|
||||
final_else: Box::new(
|
||||
// denominator was zero
|
||||
no_region(tag(
|
||||
"Err",
|
||||
vec![tag("DivByZero", Vec::new(), var_store)],
|
||||
var_store,
|
||||
)),
|
||||
),
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)],
|
||||
var_store,
|
||||
body,
|
||||
ret_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.first : List elem -> Result elem [ ListWasEmpty ]*
|
||||
///
|
||||
/// List.first :
|
||||
|
|
|
@ -18,7 +18,7 @@ use roc_region::all::Located;
|
|||
/// Just (Just a)
|
||||
/// List (List a)
|
||||
/// reverse (reverse l)
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Parens {
|
||||
NotNeeded,
|
||||
InFunctionType,
|
||||
|
|
|
@ -133,10 +133,14 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
|||
}
|
||||
}
|
||||
ParensAround(sub_expr) => {
|
||||
if parens == Parens::NotNeeded && !sub_expr_requests_parens(sub_expr) {
|
||||
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
} else {
|
||||
buf.push('(');
|
||||
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
Str(literal) => {
|
||||
use roc_parse::ast::StrLiteral::*;
|
||||
|
||||
|
@ -315,7 +319,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
|||
buf.push_str(key);
|
||||
}
|
||||
Access(expr, key) => {
|
||||
expr.format_with_options(buf, parens, Newlines::Yes, indent);
|
||||
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
|
||||
buf.push('.');
|
||||
buf.push_str(key);
|
||||
}
|
||||
|
@ -394,6 +398,8 @@ fn fmt_bin_ops<'a>(
|
|||
|| lefts.iter().any(|(expr, _)| expr.value.is_multiline());
|
||||
|
||||
for (loc_left_side, loc_bin_op) in lefts {
|
||||
let bin_op = loc_bin_op.value;
|
||||
|
||||
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent);
|
||||
|
||||
if is_multiline {
|
||||
|
@ -402,7 +408,7 @@ fn fmt_bin_ops<'a>(
|
|||
buf.push(' ');
|
||||
}
|
||||
|
||||
push_op(buf, loc_bin_op.value);
|
||||
push_op(buf, bin_op);
|
||||
|
||||
buf.push(' ');
|
||||
}
|
||||
|
@ -1046,3 +1052,32 @@ fn format_field_multiline<'a, T>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
||||
match expr {
|
||||
Expr::BinOps(left_side, _) => {
|
||||
left_side
|
||||
.iter()
|
||||
.any(|(_, loc_bin_op)| match loc_bin_op.value {
|
||||
BinOp::Caret
|
||||
| BinOp::Star
|
||||
| BinOp::Slash
|
||||
| BinOp::DoubleSlash
|
||||
| BinOp::Percent
|
||||
| BinOp::DoublePercent
|
||||
| BinOp::Plus
|
||||
| BinOp::Minus
|
||||
| BinOp::Equals
|
||||
| BinOp::NotEquals
|
||||
| BinOp::LessThan
|
||||
| BinOp::GreaterThan
|
||||
| BinOp::LessThanOrEq
|
||||
| BinOp::GreaterThanOrEq
|
||||
| BinOp::And
|
||||
| BinOp::Or => true,
|
||||
BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false,
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -362,32 +362,57 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn defs_with_defs() {
|
||||
// expr_formats_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x =
|
||||
// y = 4
|
||||
// z = 8
|
||||
// w
|
||||
#[test]
|
||||
fn excess_parens() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
x = (5)
|
||||
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x =
|
||||
// y = 4
|
||||
// z = 8
|
||||
|
||||
// w
|
||||
y = ((10))
|
||||
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
42
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
y = 10
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defs_with_defs() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
x =
|
||||
y = 4
|
||||
z = 8
|
||||
w
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
x =
|
||||
y = 4
|
||||
z = 8
|
||||
|
||||
w
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_between_two_defs() {
|
||||
|
@ -548,15 +573,16 @@ mod test_fmt {
|
|||
));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn record_field_destructuring() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// when foo is
|
||||
// { x: 5 } -> 42
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
#[test]
|
||||
fn record_field_destructuring() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
{ x: 5 } ->
|
||||
42
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_updating() {
|
||||
|
@ -917,24 +943,43 @@ mod test_fmt {
|
|||
"#
|
||||
));
|
||||
|
||||
// expr_formats_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// identity = \a
|
||||
// -> a
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \a
|
||||
-> a
|
||||
|
||||
// identity 41
|
||||
// "#
|
||||
// ),
|
||||
// indoc!(
|
||||
// r#"
|
||||
// identity = \a ->
|
||||
// a
|
||||
identity 41
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \a -> a
|
||||
|
||||
// identity 41
|
||||
// "#
|
||||
// ),
|
||||
// );
|
||||
identity 41
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \a
|
||||
->
|
||||
a + b
|
||||
|
||||
identity 4010
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \a ->
|
||||
a + b
|
||||
|
||||
identity 4010
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
|
@ -956,7 +1001,7 @@ mod test_fmt {
|
|||
// identity 43
|
||||
// "#
|
||||
// ));
|
||||
//
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
identity = \a,
|
||||
|
@ -1212,17 +1257,17 @@ mod test_fmt {
|
|||
}
|
||||
#[test]
|
||||
fn multi_line_list_def() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// l =
|
||||
// [
|
||||
// 1,
|
||||
// 2
|
||||
// ]
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
l =
|
||||
[
|
||||
1,
|
||||
2,
|
||||
]
|
||||
|
||||
// l
|
||||
// "#
|
||||
// ));
|
||||
l
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
|
@ -1248,32 +1293,32 @@ mod test_fmt {
|
|||
),
|
||||
);
|
||||
|
||||
// expr_formats_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// results =
|
||||
// # Let's count past 6
|
||||
// [
|
||||
// Ok 6,
|
||||
// Err CountError
|
||||
// ]
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
results =
|
||||
# Let's count past 6
|
||||
[
|
||||
Ok 6,
|
||||
Err CountError
|
||||
]
|
||||
|
||||
// allOks results
|
||||
// "#
|
||||
// ),
|
||||
// indoc!(
|
||||
// r#"
|
||||
// results =
|
||||
// # Let's count past 6
|
||||
// [
|
||||
// Ok 6,
|
||||
// Err CountError
|
||||
// ]
|
||||
allOks results
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
results =
|
||||
# Let's count past 6
|
||||
[
|
||||
Ok 6,
|
||||
Err CountError,
|
||||
]
|
||||
|
||||
// allOks results
|
||||
// "#
|
||||
// ),
|
||||
// );
|
||||
allOks results
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// RECORD LITERALS
|
||||
|
@ -1330,18 +1375,18 @@ mod test_fmt {
|
|||
|
||||
#[test]
|
||||
fn multi_line_record_def() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// pos =
|
||||
// {
|
||||
// x: 4,
|
||||
// y: 11,
|
||||
// z: 16
|
||||
// }
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
pos =
|
||||
{
|
||||
x: 4,
|
||||
y: 11,
|
||||
z: 16,
|
||||
}
|
||||
|
||||
// pos
|
||||
// "#
|
||||
// ));
|
||||
pos
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
|
@ -2204,7 +2249,7 @@ mod test_fmt {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn precedence_conflict_exponents() {
|
||||
fn binop_parens() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
if 4 == (6 ^ 6 ^ 7 ^ 8) then
|
||||
|
@ -2213,6 +2258,39 @@ mod test_fmt {
|
|||
"Naturally"
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
if 5 == 1 ^ 1 ^ 1 ^ 1 then
|
||||
"Not buying it"
|
||||
else
|
||||
"True"
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
if (1 == 1)
|
||||
&& (2 == 1) && (3 == 2) then
|
||||
"true"
|
||||
else
|
||||
"false"
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
if
|
||||
(1 == 1)
|
||||
&& (2 == 1)
|
||||
&& (3 == 2)
|
||||
then
|
||||
"true"
|
||||
else
|
||||
"false"
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -955,6 +955,8 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
|
|||
op,
|
||||
function_owns_closure_data,
|
||||
specialization_id,
|
||||
function_name,
|
||||
function_env,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
..
|
||||
|
@ -973,7 +975,8 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
|
|||
arg_layouts,
|
||||
ret_layout,
|
||||
*function_owns_closure_data,
|
||||
arguments,
|
||||
*function_name,
|
||||
function_env,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -4462,41 +4465,39 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids: &mut LayoutIds<'a>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
return_layout: &Layout<'a>,
|
||||
op: LowLevel,
|
||||
op: roc_mono::low_level::HigherOrder,
|
||||
func_spec: FuncSpec,
|
||||
argument_layouts: &[Layout<'a>],
|
||||
result_layout: &Layout<'a>,
|
||||
function_owns_closure_data: bool,
|
||||
args: &[Symbol],
|
||||
function_name: Symbol,
|
||||
function_env: &Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use LowLevel::*;
|
||||
|
||||
debug_assert!(op.is_higher_order());
|
||||
use roc_mono::low_level::HigherOrder::*;
|
||||
|
||||
// macros because functions cause lifetime issues related to the `env` or `layout_ids`
|
||||
macro_rules! passed_function_at_index {
|
||||
($index:expr) => {{
|
||||
let function_symbol = args[$index];
|
||||
|
||||
function_value_by_func_spec(
|
||||
macro_rules! function_details {
|
||||
() => {{
|
||||
let function = function_value_by_func_spec(
|
||||
env,
|
||||
func_spec,
|
||||
function_symbol,
|
||||
function_name,
|
||||
argument_layouts,
|
||||
return_layout,
|
||||
)
|
||||
);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, function_env);
|
||||
|
||||
(function, closure, closure_layout)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! list_walk {
|
||||
($variant:expr) => {{
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
($variant:expr, $xs:expr, $state:expr) => {{
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &$xs);
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &$state);
|
||||
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
|
||||
let function = passed_function_at_index!(2);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => default,
|
||||
|
@ -4530,15 +4531,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
}};
|
||||
}
|
||||
match op {
|
||||
ListMap => {
|
||||
ListMap { xs } => {
|
||||
// List.map : List before, (before -> after) -> List after
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
|
||||
|
@ -4563,14 +4560,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
ListMap2 => {
|
||||
debug_assert_eq!(args.len(), 4);
|
||||
ListMap2 { xs, ys } => {
|
||||
let (list1, list1_layout) = load_symbol_and_layout(scope, &xs);
|
||||
let (list2, list2_layout) = load_symbol_and_layout(scope, &ys);
|
||||
|
||||
let (list1, list1_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
|
||||
let function = passed_function_at_index!(2);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match (list1_layout, list2_layout, return_layout) {
|
||||
(
|
||||
|
@ -4606,15 +4600,12 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
ListMap3 => {
|
||||
debug_assert_eq!(args.len(), 5);
|
||||
ListMap3 { xs, ys, zs } => {
|
||||
let (list1, list1_layout) = load_symbol_and_layout(scope, &xs);
|
||||
let (list2, list2_layout) = load_symbol_and_layout(scope, &ys);
|
||||
let (list3, list3_layout) = load_symbol_and_layout(scope, &zs);
|
||||
|
||||
let (list1, list1_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
let (list3, list3_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
let function = passed_function_at_index!(3);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[4]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match (list1_layout, list2_layout, list3_layout, return_layout) {
|
||||
(
|
||||
|
@ -4655,15 +4646,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
ListMapWithIndex => {
|
||||
ListMapWithIndex { xs } => {
|
||||
// List.mapWithIndex : List before, (Nat, before -> after) -> List after
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
|
||||
|
@ -4688,15 +4675,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
ListKeepIf => {
|
||||
ListKeepIf { xs } => {
|
||||
// List.keepIf : List elem, (elem -> Bool) -> List elem
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
|
||||
|
@ -4718,15 +4701,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
ListKeepOks => {
|
||||
ListKeepOks { xs } => {
|
||||
// List.keepOks : List before, (before -> Result after *) -> List after
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(_, Layout::Builtin(Builtin::EmptyList))
|
||||
|
@ -4762,15 +4741,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
ListKeepErrs => {
|
||||
ListKeepErrs { xs } => {
|
||||
// List.keepErrs : List before, (before -> Result * after) -> List after
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(_, Layout::Builtin(Builtin::EmptyList))
|
||||
|
@ -4806,24 +4781,20 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
ListWalk => {
|
||||
list_walk!(crate::llvm::build_list::ListWalk::Walk)
|
||||
ListWalk { xs, state } => {
|
||||
list_walk!(crate::llvm::build_list::ListWalk::Walk, xs, state)
|
||||
}
|
||||
ListWalkUntil => {
|
||||
list_walk!(crate::llvm::build_list::ListWalk::WalkUntil)
|
||||
ListWalkUntil { xs, state } => {
|
||||
list_walk!(crate::llvm::build_list::ListWalk::WalkUntil, xs, state)
|
||||
}
|
||||
ListWalkBackwards => {
|
||||
list_walk!(crate::llvm::build_list::ListWalk::WalkBackwards)
|
||||
ListWalkBackwards { xs, state } => {
|
||||
list_walk!(crate::llvm::build_list::ListWalk::WalkBackwards, xs, state)
|
||||
}
|
||||
ListSortWith => {
|
||||
ListSortWith { xs } => {
|
||||
// List.sortWith : List a, (a, a -> Ordering) -> List a
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
|
||||
|
@ -4858,13 +4829,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
DictWalk => {
|
||||
debug_assert_eq!(args.len(), 4);
|
||||
DictWalk { xs, state } => {
|
||||
let (dict, dict_layout) = load_symbol_and_layout(scope, &xs);
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &state);
|
||||
|
||||
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
let function = passed_function_at_index!(2);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]);
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
||||
match dict_layout {
|
||||
Layout::Builtin(Builtin::EmptyDict) => {
|
||||
|
@ -4897,7 +4866,6 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!("invalid dict layout"),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5333,8 +5301,8 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
|
||||
| NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt
|
||||
| NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => {
|
||||
| NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumDivCeilUnchecked
|
||||
| NumPow | NumPowInt | NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => {
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
@ -6079,13 +6047,16 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
phi.as_basic_value()
|
||||
}
|
||||
}
|
||||
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
|
||||
NumPowInt => call_bitcode_int_fn(
|
||||
env,
|
||||
bitcode::NUM_POW_INT,
|
||||
&[lhs.into(), rhs.into()],
|
||||
int_width,
|
||||
),
|
||||
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
|
||||
NumDivCeilUnchecked => {
|
||||
call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_DIV_CEIL)
|
||||
}
|
||||
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
|
||||
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
|
||||
NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(),
|
||||
|
|
|
@ -736,11 +736,40 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
|
|||
|
||||
builder.position_at_end(modification_block);
|
||||
|
||||
if element_layout.contains_refcounted() {
|
||||
let ptr_type = basic_type_from_layout(env, element_layout).ptr_type(AddressSpace::Generic);
|
||||
|
||||
if element_layout.contains_refcounted() {
|
||||
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
|
||||
|
||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, ptr);
|
||||
let call_mode = mode_to_call_mode(fn_val, mode);
|
||||
|
||||
match mode {
|
||||
Mode::Inc => {
|
||||
// inc is cheap; we never recurse
|
||||
refcount_ptr.modify(call_mode, layout, env);
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
|
||||
Mode::Dec => {
|
||||
let do_recurse_block = env.context.append_basic_block(parent, "do_recurse");
|
||||
let no_recurse_block = env.context.append_basic_block(parent, "no_recurse");
|
||||
|
||||
builder.build_conditional_branch(
|
||||
refcount_ptr.is_1(env),
|
||||
do_recurse_block,
|
||||
no_recurse_block,
|
||||
);
|
||||
|
||||
{
|
||||
env.builder.position_at_end(no_recurse_block);
|
||||
|
||||
refcount_ptr.modify(call_mode, layout, env);
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
|
||||
{
|
||||
env.builder.position_at_end(do_recurse_block);
|
||||
|
||||
let loop_fn = |_index, element| {
|
||||
modify_refcount_layout_help(
|
||||
env,
|
||||
|
@ -754,13 +783,21 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
|
|||
};
|
||||
|
||||
incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn);
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// just increment/decrement the list itself, don't touch the elements
|
||||
let (_, ptr) = load_list(env.builder, original_wrapper, ptr_type);
|
||||
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
|
||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, ptr);
|
||||
let call_mode = mode_to_call_mode(fn_val, mode);
|
||||
|
||||
refcount_ptr.modify(call_mode, layout, env);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
|
|
1
compiler/gen_wasm/.gitignore
vendored
1
compiler/gen_wasm/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
*.wasm
|
||||
*.wat
|
||||
/notes.md
|
||||
/tmp
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
- [x] Push and pop stack frames
|
||||
- [x] Deal with returning structs
|
||||
- [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc.
|
||||
- [ ] Ensure early Return statements don't skip stack cleanup
|
||||
- [x] Ensure early Return statements don't skip stack cleanup
|
||||
- [x] Model the stack machine as a storage mechanism, to make generated code "less bad"
|
||||
- [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec`
|
||||
- [ ] Implement relocations
|
||||
- Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec<Instruction>` rather than a `Vec<u8>`. It may be worth serialising each instruction as it is inserted.
|
||||
|
||||
- Refactor for code sharing with CPU backends
|
||||
|
||||
- [ ] Implement a `scan_ast` pre-pass like `Backend` does, but for reusing Wasm locals rather than CPU registers
|
||||
- [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing
|
||||
- [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible
|
||||
|
||||
|
@ -88,7 +88,14 @@ main =
|
|||
1 + 2 + 4
|
||||
```
|
||||
|
||||
|
||||
### Direct translation of Mono IR
|
||||
|
||||
The Mono IR contains two functions, `Num.add` and `main`, so we generate two corresponding WebAssembly functions.
|
||||
Since it has a Symbol for every expression, the simplest thing is to create a local for each one.
|
||||
The code ends up being quite bloated, with lots of `local.set` and `local.get` instructions.
|
||||
|
||||
I've added comments on each line to show what is on the stack and in the locals at each point in the program.
|
||||
|
||||
```
|
||||
(func (;0;) (param i64 i64) (result i64) ; declare function index 0 (Num.add) with two i64 parameters and an i64 result
|
||||
|
@ -115,13 +122,10 @@ The Mono IR contains two functions, `Num.add` and `main`, so we generate two cor
|
|||
return) ; return the value at the top of the stack
|
||||
```
|
||||
|
||||
If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away (which is all of them in this example!). The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first).
|
||||
### Handwritten equivalent
|
||||
|
||||
```
|
||||
$ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm
|
||||
```
|
||||
|
||||
The optimised functions have no local variables at all for this example. (Of course, this is an oversimplified toy example! It might not be so extreme in a real program.)
|
||||
This code doesn't actually require any locals at all.
|
||||
(It also doesn't need the `return` instructions, but that's less of a problem.)
|
||||
|
||||
```
|
||||
(func (;0;) (param i64 i64) (result i64)
|
||||
|
@ -138,13 +142,36 @@ The optimised functions have no local variables at all for this example. (Of cou
|
|||
|
||||
### Reducing sets and gets
|
||||
|
||||
It would be nice to find some cheap optimisation to reduce the number of `local.set` and `local.get` instructions.
|
||||
For our example code, we don't need any locals because the WebAssembly virtual machine effectively _stores_ intermediate results in a stack. Since it's already storing those values, there is no need for us to create locals. If you compare the two versions, you'll see that the `local.set` and `local.get` instructions have simply been deleted and the other instructions are in the same order.
|
||||
|
||||
We don't need a `local` if the value we want is already at the top of the VM stack. In fact, for our example above, it just so happens that if we simply skip generating the `local.set` instructions, everything _does_ appear on the VM stack in the right order, which means we can skip the `local.get` too. It ends up being very close to the fully optimised version! I assume this is because the Mono IR within the function is in dependency order, but I'm not sure...
|
||||
But sometimes we really do need locals! We may need to use the same symbol twice, or values could end up on the stack in the wrong order and need to be swapped around by setting a local and getting it again.
|
||||
|
||||
Of course the trick is to do this reliably for more complex dependency graphs. I am investigating whether we can do it by optimistically assuming it's OK not to create a local, and then keeping track of which symbols are at which positions in the VM stack after every instruction. Then when we need to use a symbol we can first check if it's on the VM stack and only create a local if it's not. In cases where we _do_ need to create a local, we need to go back and insert a `local.set` instruction at an earlier point in the program. We can make this fast by waiting to do all of the insertions in one batch when we're finalising the procedure.
|
||||
The hard part is knowing when we need a local, and when we don't. For that, the `WasmBackend` needs to have some understanding of the stack machine.
|
||||
|
||||
For a while we thought it would be very helpful to reuse the same local for multiple symbols at different points in the program. And we already have similar code in the CPU backends for register allocation. But on further examination, it doesn't actually buy us much! In our example above, we would still have the same number of `local.set` and `local.get` instructions - they'd just be operating on two locals instead of four! That doesn't shrink much code. Only the declaration at the top of the function would shrink from `(local i64 i64 i64 i64)` to `(local i64 i64)`... and in fact that's only smaller in the text format, it's the same size in the binary format! So the `scan_ast` pass doesn't seem worthwhile for Wasm.
|
||||
To help with this, the `CodeBuilder` maintains a vector that represents the stack. For every instruction the backend generates, `CodeBuilder` simulates the right number of pushes and pops for that instruction, so that we always know the state of the VM stack at every point in the program.
|
||||
|
||||
When the `WasmBackend` generates code for a `Let` statement, it can "label" the top of the stack with the relevant `Symbol`. Then at any later point in the program, when we need to retrieve a list of symbols in a certain order, we can check whether they already happen to be at the top of the stack in that order (as they were in our example above.)
|
||||
|
||||
In practice it should be very common for values to appear on the VM stack in the right order, because in the Mono IR, statements occur in dependency order! We should only generate locals when the dependency graph is a little more complicated, and we actually need them.
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────┐
|
||||
│ │ │ │
|
||||
│ ├─────► Storage ├──────┐
|
||||
│ │ │ │ │
|
||||
│ │ └─────────────┘ │
|
||||
│ │ Manage state about │
|
||||
│ │ how/where symbol │ Delegate part of
|
||||
│ WasmBackend │ values are stored │ state management
|
||||
│ │ │ for values on
|
||||
│ │ │ the VM stack
|
||||
│ │ │
|
||||
│ │ Generate ┌────────▼──────┐
|
||||
│ │ instructions │ │
|
||||
│ ├─────────────────► CodeBuilder │
|
||||
│ │ │ │
|
||||
└─────────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
## Memory
|
||||
|
||||
|
|
|
@ -10,12 +10,10 @@ use roc_module::symbol::Symbol;
|
|||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
|
||||
use crate::code_builder::CodeBuilder;
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::storage::{StackMemoryLocation, SymbolStorage};
|
||||
use crate::{
|
||||
copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig,
|
||||
LocalId, PTR_SIZE, PTR_TYPE,
|
||||
};
|
||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||
use crate::{copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, LocalId, PTR_TYPE};
|
||||
|
||||
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
|
||||
// Follow Emscripten's example by using 1kB (4 bytes would probably do)
|
||||
|
@ -24,84 +22,71 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
struct LabelId(u32);
|
||||
|
||||
enum LocalKind {
|
||||
Parameter,
|
||||
Variable,
|
||||
}
|
||||
|
||||
// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43)
|
||||
pub struct WasmBackend<'a> {
|
||||
// Module: Wasm AST
|
||||
pub builder: ModuleBuilder,
|
||||
// Module level: Wasm AST
|
||||
pub module_builder: ModuleBuilder,
|
||||
|
||||
// Module: internal state & IR mappings
|
||||
// Module level: internal state & IR mappings
|
||||
_data_offset_map: MutMap<Literal<'a>, u32>,
|
||||
_data_offset_next: u32,
|
||||
proc_symbol_map: MutMap<Symbol, CodeLocation>,
|
||||
|
||||
// Functions: Wasm AST
|
||||
instructions: std::vec::Vec<Instruction>,
|
||||
arg_types: std::vec::Vec<ValueType>,
|
||||
locals: std::vec::Vec<Local>,
|
||||
// Function level
|
||||
code_builder: CodeBuilder,
|
||||
storage: Storage,
|
||||
|
||||
// Functions: internal state & IR mappings
|
||||
stack_memory: i32,
|
||||
stack_frame_pointer: Option<LocalId>,
|
||||
symbol_storage_map: MutMap<Symbol, SymbolStorage>,
|
||||
/// how many blocks deep are we (used for jumps)
|
||||
block_depth: u32,
|
||||
joinpoint_label_map: MutMap<JoinPointId, (u32, std::vec::Vec<LocalId>)>,
|
||||
joinpoint_label_map: MutMap<JoinPointId, (u32, std::vec::Vec<StoredValue>)>,
|
||||
}
|
||||
|
||||
impl<'a> WasmBackend<'a> {
|
||||
pub fn new() -> Self {
|
||||
WasmBackend {
|
||||
// Module: Wasm AST
|
||||
builder: builder::module(),
|
||||
module_builder: builder::module(),
|
||||
|
||||
// Module: internal state & IR mappings
|
||||
_data_offset_map: MutMap::default(),
|
||||
_data_offset_next: UNUSED_DATA_SECTION_BYTES,
|
||||
proc_symbol_map: MutMap::default(),
|
||||
|
||||
// Functions: Wasm AST
|
||||
instructions: std::vec::Vec::with_capacity(256),
|
||||
arg_types: std::vec::Vec::with_capacity(8),
|
||||
locals: std::vec::Vec::with_capacity(32),
|
||||
|
||||
// Functions: internal state & IR mappings
|
||||
stack_memory: 0,
|
||||
stack_frame_pointer: None,
|
||||
symbol_storage_map: MutMap::default(),
|
||||
block_depth: 0,
|
||||
joinpoint_label_map: MutMap::default(),
|
||||
|
||||
// Functions
|
||||
code_builder: CodeBuilder::new(),
|
||||
storage: Storage::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
// Functions: Wasm AST
|
||||
self.instructions.clear();
|
||||
self.arg_types.clear();
|
||||
self.locals.clear();
|
||||
|
||||
// Functions: internal state & IR mappings
|
||||
self.stack_memory = 0;
|
||||
self.stack_frame_pointer = None;
|
||||
self.symbol_storage_map.clear();
|
||||
self.code_builder.clear();
|
||||
self.storage.clear();
|
||||
self.joinpoint_label_map.clear();
|
||||
assert_eq!(self.block_depth, 0);
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
PROCEDURE
|
||||
|
||||
***********************************************************/
|
||||
|
||||
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
|
||||
// println!("\ngenerating procedure {:?}\n", sym);
|
||||
|
||||
let signature_builder = self.start_proc(&proc);
|
||||
|
||||
self.build_stmt(&proc.body, &proc.ret_layout)?;
|
||||
|
||||
let function_def = self.finalize_proc(signature_builder);
|
||||
let location = self.builder.push_function(function_def);
|
||||
let location = self.module_builder.push_function(function_def);
|
||||
let function_index = location.body;
|
||||
self.proc_symbol_map.insert(sym, location);
|
||||
self.reset();
|
||||
// println!("\nfinished generating {:?}\n", sym);
|
||||
|
||||
Ok(function_index)
|
||||
}
|
||||
|
@ -110,7 +95,7 @@ impl<'a> WasmBackend<'a> {
|
|||
let ret_layout = WasmLayout::new(&proc.ret_layout);
|
||||
|
||||
let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
|
||||
self.arg_types.push(PTR_TYPE);
|
||||
self.storage.arg_types.push(PTR_TYPE);
|
||||
self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
|
||||
builder::signature()
|
||||
} else {
|
||||
|
@ -120,213 +105,117 @@ impl<'a> WasmBackend<'a> {
|
|||
};
|
||||
|
||||
for (layout, symbol) in proc.args {
|
||||
self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter);
|
||||
self.storage.allocate(
|
||||
&WasmLayout::new(layout),
|
||||
*symbol,
|
||||
StoredValueKind::Parameter,
|
||||
);
|
||||
}
|
||||
|
||||
signature_builder.with_params(self.arg_types.clone())
|
||||
signature_builder.with_params(self.storage.arg_types.clone())
|
||||
}
|
||||
|
||||
fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition {
|
||||
self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any)
|
||||
|
||||
let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10);
|
||||
const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10;
|
||||
let mut final_instructions =
|
||||
Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN);
|
||||
|
||||
if self.stack_memory > 0 {
|
||||
if self.storage.stack_frame_size > 0 {
|
||||
push_stack_frame(
|
||||
&mut final_instructions,
|
||||
self.stack_memory,
|
||||
self.stack_frame_pointer.unwrap(),
|
||||
self.storage.stack_frame_size,
|
||||
self.storage.stack_frame_pointer.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
final_instructions.extend(self.instructions.drain(0..));
|
||||
self.code_builder.finalize_into(&mut final_instructions);
|
||||
|
||||
if self.stack_memory > 0 {
|
||||
if self.storage.stack_frame_size > 0 {
|
||||
pop_stack_frame(
|
||||
&mut final_instructions,
|
||||
self.stack_memory,
|
||||
self.stack_frame_pointer.unwrap(),
|
||||
self.storage.stack_frame_size,
|
||||
self.storage.stack_frame_pointer.unwrap(),
|
||||
);
|
||||
}
|
||||
final_instructions.push(End);
|
||||
|
||||
// Declare local variables (in batches of the same type)
|
||||
let num_locals = self.storage.local_types.len();
|
||||
let mut locals = Vec::with_capacity(num_locals);
|
||||
if num_locals > 0 {
|
||||
let mut batch_type = self.storage.local_types[0];
|
||||
let mut batch_size = 0;
|
||||
for t in &self.storage.local_types {
|
||||
if *t == batch_type {
|
||||
batch_size += 1;
|
||||
} else {
|
||||
locals.push(Local::new(batch_size, batch_type));
|
||||
batch_type = *t;
|
||||
batch_size = 1;
|
||||
}
|
||||
}
|
||||
locals.push(Local::new(batch_size, batch_type));
|
||||
}
|
||||
|
||||
builder::function()
|
||||
.with_signature(signature_builder.build_sig())
|
||||
.body()
|
||||
.with_locals(self.locals.clone())
|
||||
.with_locals(locals)
|
||||
.with_instructions(Instructions::new(final_instructions))
|
||||
.build() // body
|
||||
.build() // function
|
||||
}
|
||||
|
||||
fn insert_local(
|
||||
&mut self,
|
||||
wasm_layout: WasmLayout,
|
||||
symbol: Symbol,
|
||||
kind: LocalKind,
|
||||
) -> Option<LocalId> {
|
||||
let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32);
|
||||
/**********************************************************
|
||||
|
||||
match kind {
|
||||
LocalKind::Parameter => {
|
||||
self.arg_types.push(wasm_layout.value_type());
|
||||
}
|
||||
LocalKind::Variable => {
|
||||
self.locals.push(Local::new(1, wasm_layout.value_type()));
|
||||
}
|
||||
}
|
||||
STATEMENTS
|
||||
|
||||
let (maybe_local_id, storage) = match wasm_layout {
|
||||
WasmLayout::LocalOnly(value_type, size) => (
|
||||
Some(next_local_id),
|
||||
SymbolStorage::Local {
|
||||
local_id: next_local_id,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
),
|
||||
|
||||
WasmLayout::HeapMemory => (
|
||||
Some(next_local_id),
|
||||
SymbolStorage::Local {
|
||||
local_id: next_local_id,
|
||||
value_type: PTR_TYPE,
|
||||
size: PTR_SIZE,
|
||||
},
|
||||
),
|
||||
|
||||
WasmLayout::StackMemory {
|
||||
size,
|
||||
alignment_bytes,
|
||||
} => {
|
||||
let location = match kind {
|
||||
LocalKind::Parameter => StackMemoryLocation::PointerArg(next_local_id),
|
||||
|
||||
LocalKind::Variable => {
|
||||
match self.stack_frame_pointer {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
self.stack_frame_pointer = Some(next_local_id);
|
||||
}
|
||||
};
|
||||
|
||||
let offset =
|
||||
round_up_to_alignment(self.stack_memory, alignment_bytes as i32);
|
||||
|
||||
self.stack_memory = offset + size as i32;
|
||||
|
||||
StackMemoryLocation::FrameOffset(offset as u32)
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
None,
|
||||
SymbolStorage::StackMemory {
|
||||
location,
|
||||
size,
|
||||
alignment_bytes,
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
self.symbol_storage_map.insert(symbol, storage);
|
||||
|
||||
maybe_local_id
|
||||
}
|
||||
|
||||
fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage {
|
||||
self.symbol_storage_map.get(sym).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Symbol {:?} not found in function scope:\n{:?}",
|
||||
sym, self.symbol_storage_map
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId {
|
||||
let storage = self.get_symbol_storage(sym);
|
||||
match storage {
|
||||
SymbolStorage::Local { local_id, .. } => *local_id,
|
||||
_ => {
|
||||
panic!("{:?} does not have a local_id", sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a symbol, e.g. for passing to a function call
|
||||
fn load_symbol(&mut self, sym: &Symbol) {
|
||||
let storage = self.get_symbol_storage(sym).to_owned();
|
||||
match storage {
|
||||
SymbolStorage::Local { local_id, .. }
|
||||
| SymbolStorage::StackMemory {
|
||||
location: StackMemoryLocation::PointerArg(local_id),
|
||||
..
|
||||
} => {
|
||||
self.instructions.push(GetLocal(local_id.0));
|
||||
}
|
||||
|
||||
SymbolStorage::StackMemory {
|
||||
location: StackMemoryLocation::FrameOffset(offset),
|
||||
..
|
||||
} => {
|
||||
self.instructions.extend([
|
||||
GetLocal(self.stack_frame_pointer.unwrap().0),
|
||||
I32Const(offset as i32),
|
||||
I32Add,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
***********************************************************/
|
||||
|
||||
/// start a loop that leaves a value on the stack
|
||||
fn start_loop_with_return(&mut self, value_type: ValueType) {
|
||||
self.block_depth += 1;
|
||||
self.instructions.push(Loop(BlockType::Value(value_type)));
|
||||
self.code_builder.push(Loop(BlockType::Value(value_type)));
|
||||
}
|
||||
|
||||
fn start_block(&mut self, block_type: BlockType) {
|
||||
self.block_depth += 1;
|
||||
self.instructions.push(Block(block_type));
|
||||
self.code_builder.push(Block(block_type));
|
||||
}
|
||||
|
||||
fn end_block(&mut self) {
|
||||
self.block_depth -= 1;
|
||||
self.instructions.push(End);
|
||||
self.code_builder.push(End);
|
||||
}
|
||||
|
||||
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
|
||||
match stmt {
|
||||
// Simple optimisation: if we are just returning the expression, we don't need a local
|
||||
Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => {
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
if let WasmLayout::StackMemory {
|
||||
size,
|
||||
alignment_bytes,
|
||||
} = wasm_layout
|
||||
{
|
||||
// Map this symbol to the first argument (pointer into caller's stack)
|
||||
// Saves us from having to copy it later
|
||||
let storage = SymbolStorage::StackMemory {
|
||||
location: StackMemoryLocation::PointerArg(LocalId(0)),
|
||||
size,
|
||||
alignment_bytes,
|
||||
};
|
||||
self.symbol_storage_map.insert(*let_sym, storage);
|
||||
}
|
||||
self.build_expr(let_sym, expr, layout)?;
|
||||
self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Let(sym, expr, layout, following) => {
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
let maybe_local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable);
|
||||
|
||||
let kind = match following {
|
||||
Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue,
|
||||
_ => StoredValueKind::Variable,
|
||||
};
|
||||
|
||||
self.storage.allocate(&wasm_layout, *sym, kind);
|
||||
|
||||
self.build_expr(sym, expr, layout)?;
|
||||
|
||||
if let Some(local_id) = maybe_local_id {
|
||||
self.instructions.push(SetLocal(local_id.0));
|
||||
// For primitives, we record that this symbol is at the top of the VM stack
|
||||
// (For other values, we wrote to memory and there's nothing on the VM stack)
|
||||
if let WasmLayout::Primitive(value_type, size) = wasm_layout {
|
||||
let vm_state = self.code_builder.set_top_symbol(*sym);
|
||||
self.storage.symbol_storage_map.insert(
|
||||
*sym,
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
self.build_stmt(following, ret_layout)?;
|
||||
|
@ -334,9 +223,9 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
Stmt::Ret(sym) => {
|
||||
use crate::storage::SymbolStorage::*;
|
||||
use crate::storage::StoredValue::*;
|
||||
|
||||
let storage = self.symbol_storage_map.get(sym).unwrap();
|
||||
let storage = self.storage.symbol_storage_map.get(sym).unwrap();
|
||||
|
||||
match storage {
|
||||
StackMemory {
|
||||
|
@ -344,15 +233,10 @@ impl<'a> WasmBackend<'a> {
|
|||
size,
|
||||
alignment_bytes,
|
||||
} => {
|
||||
let (from_ptr, from_offset) = match location {
|
||||
StackMemoryLocation::PointerArg(local_id) => (*local_id, 0),
|
||||
StackMemoryLocation::FrameOffset(offset) => {
|
||||
(self.stack_frame_pointer.unwrap(), *offset)
|
||||
}
|
||||
};
|
||||
|
||||
let (from_ptr, from_offset) =
|
||||
location.local_and_offset(self.storage.stack_frame_pointer);
|
||||
copy_memory(
|
||||
&mut self.instructions,
|
||||
&mut self.code_builder,
|
||||
CopyMemoryConfig {
|
||||
from_ptr,
|
||||
from_offset,
|
||||
|
@ -364,9 +248,9 @@ impl<'a> WasmBackend<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
Local { local_id, .. } => {
|
||||
self.instructions.push(GetLocal(local_id.0));
|
||||
self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop)
|
||||
_ => {
|
||||
self.storage.load_symbols(&mut self.code_builder, &[*sym]);
|
||||
self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,26 +268,33 @@ impl<'a> WasmBackend<'a> {
|
|||
// We may be able to improve this in the future with `Select`
|
||||
// or `BrTable`
|
||||
|
||||
// Ensure the condition value is not stored only in the VM stack
|
||||
// Otherwise we can't reach it from inside the block
|
||||
let cond_storage = self.storage.get(cond_symbol).to_owned();
|
||||
self.storage.ensure_value_has_local(
|
||||
&mut self.code_builder,
|
||||
*cond_symbol,
|
||||
cond_storage,
|
||||
);
|
||||
|
||||
// create (number_of_branches - 1) new blocks.
|
||||
for _ in 0..branches.len() {
|
||||
self.start_block(BlockType::NoResult)
|
||||
}
|
||||
|
||||
// the LocalId of the symbol that we match on
|
||||
let matched_on = self.local_id_from_symbol(cond_symbol);
|
||||
|
||||
// then, we jump whenever the value under scrutiny is equal to the value of a branch
|
||||
for (i, (value, _, _)) in branches.iter().enumerate() {
|
||||
// put the cond_symbol on the top of the stack
|
||||
self.instructions.push(GetLocal(matched_on.0));
|
||||
self.storage
|
||||
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
|
||||
|
||||
self.instructions.push(I32Const(*value as i32));
|
||||
self.code_builder.push(I32Const(*value as i32));
|
||||
|
||||
// compare the 2 topmost values
|
||||
self.instructions.push(I32Eq);
|
||||
self.code_builder.push(I32Eq);
|
||||
|
||||
// "break" out of `i` surrounding blocks
|
||||
self.instructions.push(BrIf(i as u32));
|
||||
self.code_builder.push(BrIf(i as u32));
|
||||
}
|
||||
|
||||
// if we never jumped because a value matched, we're in the default case
|
||||
|
@ -427,19 +318,26 @@ impl<'a> WasmBackend<'a> {
|
|||
remainder,
|
||||
} => {
|
||||
// make locals for join pointer parameters
|
||||
let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len());
|
||||
let mut jp_param_storages = std::vec::Vec::with_capacity(parameters.len());
|
||||
for parameter in parameters.iter() {
|
||||
let wasm_layout = WasmLayout::new(¶meter.layout);
|
||||
let maybe_local_id =
|
||||
self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable);
|
||||
let jp_param_id = maybe_local_id.unwrap();
|
||||
jp_parameter_local_ids.push(jp_param_id);
|
||||
let mut param_storage = self.storage.allocate(
|
||||
&wasm_layout,
|
||||
parameter.symbol,
|
||||
StoredValueKind::Variable,
|
||||
);
|
||||
param_storage = self.storage.ensure_value_has_local(
|
||||
&mut self.code_builder,
|
||||
parameter.symbol,
|
||||
param_storage,
|
||||
);
|
||||
jp_param_storages.push(param_storage);
|
||||
}
|
||||
|
||||
self.start_block(BlockType::NoResult);
|
||||
|
||||
self.joinpoint_label_map
|
||||
.insert(*id, (self.block_depth, jp_parameter_local_ids));
|
||||
.insert(*id, (self.block_depth, jp_param_storages));
|
||||
|
||||
self.build_stmt(remainder, ret_layout)?;
|
||||
|
||||
|
@ -458,18 +356,21 @@ impl<'a> WasmBackend<'a> {
|
|||
Ok(())
|
||||
}
|
||||
Stmt::Jump(id, arguments) => {
|
||||
let (target, locals) = &self.joinpoint_label_map[id];
|
||||
let (target, param_storages) = self.joinpoint_label_map[id].clone();
|
||||
|
||||
// put the arguments on the stack
|
||||
for (symbol, local_id) in arguments.iter().zip(locals.iter()) {
|
||||
let argument = self.local_id_from_symbol(symbol);
|
||||
self.instructions.push(GetLocal(argument.0));
|
||||
self.instructions.push(SetLocal(local_id.0));
|
||||
for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) {
|
||||
let arg_storage = self.storage.get(arg_symbol).clone();
|
||||
self.storage.clone_value(
|
||||
&mut self.code_builder,
|
||||
param_storage,
|
||||
&arg_storage,
|
||||
*arg_symbol,
|
||||
);
|
||||
}
|
||||
|
||||
// jump
|
||||
let levels = self.block_depth - target;
|
||||
self.instructions.push(Br(levels));
|
||||
self.code_builder.push(Br(levels));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -477,6 +378,12 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
EXPRESSIONS
|
||||
|
||||
***********************************************************/
|
||||
|
||||
fn build_expr(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
|
@ -491,14 +398,34 @@ impl<'a> WasmBackend<'a> {
|
|||
arguments,
|
||||
}) => match call_type {
|
||||
CallType::ByName { name: func_sym, .. } => {
|
||||
for arg in *arguments {
|
||||
self.load_symbol(arg);
|
||||
// TODO: See if we can make this more efficient
|
||||
// Recreating the same WasmLayout again, rather than passing it down,
|
||||
// to match signature of Backend::build_expr
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
|
||||
let mut wasm_args_tmp: Vec<Symbol>;
|
||||
let (wasm_args, has_return_val) = match wasm_layout {
|
||||
WasmLayout::StackMemory { .. } => {
|
||||
wasm_args_tmp = Vec::with_capacity(arguments.len() + 1); // TODO: bumpalo
|
||||
wasm_args_tmp.push(*sym);
|
||||
wasm_args_tmp.extend_from_slice(*arguments);
|
||||
(wasm_args_tmp.as_slice(), false)
|
||||
}
|
||||
_ => (*arguments, true),
|
||||
};
|
||||
|
||||
self.storage.load_symbols(&mut self.code_builder, wasm_args);
|
||||
|
||||
let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!(
|
||||
"Cannot find function {:?} called from {:?}",
|
||||
func_sym, sym
|
||||
))?;
|
||||
self.instructions.push(Call(function_location.body));
|
||||
|
||||
self.code_builder.push_call(
|
||||
function_location.body,
|
||||
wasm_args.len(),
|
||||
has_return_val,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -542,7 +469,7 @@ impl<'a> WasmBackend<'a> {
|
|||
return Err(format!("loading literal, {:?}, is not yet implemented", x));
|
||||
}
|
||||
};
|
||||
self.instructions.push(instruction);
|
||||
self.code_builder.push(instruction);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -552,20 +479,23 @@ impl<'a> WasmBackend<'a> {
|
|||
layout: &Layout<'a>,
|
||||
fields: &'a [Symbol],
|
||||
) -> Result<(), String> {
|
||||
let storage = self.get_symbol_storage(sym).to_owned();
|
||||
// TODO: we just calculated storage and now we're getting it out of a map
|
||||
// Not passing it as an argument because I'm trying to match Backend method signatures
|
||||
let storage = self.storage.get(sym).to_owned();
|
||||
|
||||
if let Layout::Struct(field_layouts) = layout {
|
||||
match storage {
|
||||
SymbolStorage::StackMemory { location, size, .. } => {
|
||||
StoredValue::StackMemory { location, size, .. } => {
|
||||
if size > 0 {
|
||||
let (local_id, struct_offset) =
|
||||
location.local_and_offset(self.stack_frame_pointer);
|
||||
location.local_and_offset(self.storage.stack_frame_pointer);
|
||||
let mut field_offset = struct_offset;
|
||||
for (field, _) in fields.iter().zip(field_layouts.iter()) {
|
||||
field_offset += self.copy_symbol_to_pointer_at_offset(
|
||||
field_offset += self.storage.copy_value_to_memory(
|
||||
&mut self.code_builder,
|
||||
local_id,
|
||||
field_offset,
|
||||
field,
|
||||
*field,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -581,40 +511,20 @@ impl<'a> WasmBackend<'a> {
|
|||
};
|
||||
} else {
|
||||
// Struct expression but not Struct layout => single element. Copy it.
|
||||
let field_storage = self.get_symbol_storage(&fields[0]).to_owned();
|
||||
storage.copy_from(
|
||||
&field_storage,
|
||||
&mut self.instructions,
|
||||
self.stack_frame_pointer,
|
||||
);
|
||||
let field_storage = self.storage.get(&fields[0]).to_owned();
|
||||
self.storage
|
||||
.clone_value(&mut self.code_builder, &storage, &field_storage, fields[0]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_symbol_to_pointer_at_offset(
|
||||
&mut self,
|
||||
to_ptr: LocalId,
|
||||
to_offset: u32,
|
||||
from_symbol: &Symbol,
|
||||
) -> u32 {
|
||||
let from_storage = self.get_symbol_storage(from_symbol).to_owned();
|
||||
from_storage.copy_to_memory(
|
||||
&mut self.instructions,
|
||||
to_ptr,
|
||||
to_offset,
|
||||
self.stack_frame_pointer,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_call_low_level(
|
||||
&mut self,
|
||||
lowlevel: &LowLevel,
|
||||
args: &'a [Symbol],
|
||||
return_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
for arg in args {
|
||||
self.load_symbol(arg);
|
||||
}
|
||||
self.storage.load_symbols(&mut self.code_builder, args);
|
||||
let wasm_layout = WasmLayout::new(return_layout);
|
||||
self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?;
|
||||
Ok(())
|
||||
|
@ -631,7 +541,6 @@ impl<'a> WasmBackend<'a> {
|
|||
// For those, we'll need to pre-process each argument before the main op,
|
||||
// so simple arrays of instructions won't work. But there are common patterns.
|
||||
let instructions: &[Instruction] = match lowlevel {
|
||||
// Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_symbol?
|
||||
LowLevel::NumAdd => match return_value_type {
|
||||
ValueType::I32 => &[I32Add],
|
||||
ValueType::I64 => &[I64Add],
|
||||
|
@ -658,7 +567,7 @@ impl<'a> WasmBackend<'a> {
|
|||
return Err(format!("unsupported low-level op {:?}", lowlevel));
|
||||
}
|
||||
};
|
||||
self.instructions.extend_from_slice(instructions);
|
||||
self.code_builder.extend_from_slice(instructions);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
456
compiler/gen_wasm/src/code_builder.rs
Normal file
456
compiler/gen_wasm/src/code_builder.rs
Normal file
|
@ -0,0 +1,456 @@
|
|||
use core::panic;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use parity_wasm::elements::{Instruction, Instruction::*};
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::LocalId;
|
||||
|
||||
const DEBUG_LOG: bool = false;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum VirtualMachineSymbolState {
|
||||
/// Value doesn't exist yet
|
||||
NotYetPushed,
|
||||
|
||||
/// Value has been pushed onto the VM stack but not yet popped
|
||||
/// Remember where it was pushed, in case we need to insert another instruction there later
|
||||
Pushed { pushed_at: usize },
|
||||
|
||||
/// Value has been pushed and popped, so it's not on the VM stack any more.
|
||||
/// If we want to use it again later, we will have to create a local for it,
|
||||
/// by going back to insert a local.tee instruction at pushed_at
|
||||
Popped { pushed_at: usize },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CodeBuilder {
|
||||
/// The main container for the instructions
|
||||
code: Vec<Instruction>,
|
||||
|
||||
/// Extra instructions to insert at specific positions during finalisation
|
||||
/// (Go back and set locals when we realise we need them)
|
||||
/// We need BTree rather than Map or Vec, to ensure keys are sorted.
|
||||
/// Entries may not be added in order. They are created when a Symbol
|
||||
/// is used for the second time, or is in an inconvenient VM stack position,
|
||||
/// so it's not a simple predictable order.
|
||||
insertions: BTreeMap<usize, Instruction>,
|
||||
|
||||
/// Our simulation model of the Wasm stack machine
|
||||
/// Keeps track of where Symbol values are in the VM stack
|
||||
vm_stack: Vec<Symbol>,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl CodeBuilder {
|
||||
pub fn new() -> Self {
|
||||
CodeBuilder {
|
||||
vm_stack: Vec::with_capacity(32),
|
||||
insertions: BTreeMap::default(),
|
||||
code: Vec::with_capacity(1024),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.code.clear();
|
||||
self.insertions.clear();
|
||||
self.vm_stack.clear();
|
||||
}
|
||||
|
||||
/// Add an instruction
|
||||
pub fn push(&mut self, inst: Instruction) {
|
||||
let (pops, push) = get_pops_and_pushes(&inst);
|
||||
let new_len = self.vm_stack.len() - pops as usize;
|
||||
self.vm_stack.truncate(new_len);
|
||||
if push {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
}
|
||||
if DEBUG_LOG {
|
||||
println!("{:?} {:?}", inst, self.vm_stack);
|
||||
}
|
||||
self.code.push(inst);
|
||||
}
|
||||
|
||||
/// Add many instructions
|
||||
pub fn extend_from_slice(&mut self, instructions: &[Instruction]) {
|
||||
let old_len = self.vm_stack.len();
|
||||
let mut len = old_len;
|
||||
let mut min_len = len;
|
||||
for inst in instructions {
|
||||
let (pops, push) = get_pops_and_pushes(inst);
|
||||
len -= pops as usize;
|
||||
if len < min_len {
|
||||
min_len = len;
|
||||
}
|
||||
if push {
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
self.vm_stack.truncate(min_len);
|
||||
self.vm_stack
|
||||
.resize(len, Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
if DEBUG_LOG {
|
||||
println!("{:?} {:?}", instructions, self.vm_stack);
|
||||
}
|
||||
self.code.extend_from_slice(instructions);
|
||||
}
|
||||
|
||||
/// Special-case method to add a Call instruction
|
||||
/// Specify the number of arguments the function pops from the VM stack, and whether it pushes a return value
|
||||
pub fn push_call(&mut self, function_index: u32, pops: usize, push: bool) {
|
||||
let stack_depth = self.vm_stack.len();
|
||||
if pops > stack_depth {
|
||||
let mut final_code = Vec::with_capacity(self.code.len() + self.insertions.len());
|
||||
self.finalize_into(&mut final_code);
|
||||
panic!(
|
||||
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\nfinal_code={:?}\nvm_stack={:?}",
|
||||
function_index, pops, stack_depth, final_code, self.vm_stack
|
||||
);
|
||||
}
|
||||
self.vm_stack.truncate(stack_depth - pops);
|
||||
if push {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
}
|
||||
let inst = Call(function_index);
|
||||
if DEBUG_LOG {
|
||||
println!("{:?} {:?}", inst, self.vm_stack);
|
||||
}
|
||||
self.code.push(inst);
|
||||
}
|
||||
|
||||
/// Finalize a function body by copying all instructions into a vector
|
||||
pub fn finalize_into(&mut self, final_code: &mut Vec<Instruction>) {
|
||||
let mut insertions_iter = self.insertions.iter();
|
||||
let mut next_insertion = insertions_iter.next();
|
||||
|
||||
for (pos, instruction) in self.code.drain(0..).enumerate() {
|
||||
match next_insertion {
|
||||
Some((&insert_pos, insert_inst)) if insert_pos == pos => {
|
||||
final_code.push(insert_inst.to_owned());
|
||||
next_insertion = insertions_iter.next();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
final_code.push(instruction);
|
||||
}
|
||||
debug_assert!(next_insertion == None);
|
||||
}
|
||||
|
||||
/// Total number of instructions in the final output
|
||||
pub fn len(&self) -> usize {
|
||||
self.code.len() + self.insertions.len()
|
||||
}
|
||||
|
||||
/// Set the Symbol that is at the top of the VM stack right now
|
||||
/// We will use this later when we need to load the Symbol
|
||||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
|
||||
let len = self.vm_stack.len();
|
||||
let pushed_at = self.code.len();
|
||||
|
||||
if len == 0 {
|
||||
panic!(
|
||||
"trying to set symbol with nothing on stack, code = {:?}",
|
||||
self.code
|
||||
);
|
||||
}
|
||||
|
||||
self.vm_stack[len - 1] = sym;
|
||||
|
||||
VirtualMachineSymbolState::Pushed { pushed_at }
|
||||
}
|
||||
|
||||
/// Verify if a sequence of symbols is at the top of the stack
|
||||
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
|
||||
let n_symbols = symbols.len();
|
||||
let stack_depth = self.vm_stack.len();
|
||||
if n_symbols > stack_depth {
|
||||
return false;
|
||||
}
|
||||
let offset = stack_depth - n_symbols;
|
||||
|
||||
for (i, sym) in symbols.iter().enumerate() {
|
||||
if self.vm_stack[offset + i] != *sym {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Load a Symbol that is stored in the VM stack
|
||||
/// If it's already at the top of the stack, no code will be generated.
|
||||
/// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided.
|
||||
///
|
||||
/// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call.
|
||||
/// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local.
|
||||
/// (In this case, the caller must remember to declare the local in the function header.)
|
||||
pub fn load_symbol(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
vm_state: VirtualMachineSymbolState,
|
||||
next_local_id: LocalId,
|
||||
) -> Option<VirtualMachineSymbolState> {
|
||||
use VirtualMachineSymbolState::*;
|
||||
|
||||
match vm_state {
|
||||
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
|
||||
|
||||
Pushed { pushed_at } => {
|
||||
let &top = self.vm_stack.last().unwrap();
|
||||
if top == symbol {
|
||||
// We're lucky, the symbol is already on top of the VM stack
|
||||
// No code to generate! (This reduces code size by up to 25% in tests.)
|
||||
// Just let the caller know what happened
|
||||
Some(Popped { pushed_at })
|
||||
} else {
|
||||
// Symbol is not on top of the stack. Find it.
|
||||
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
|
||||
// Insert a SetLocal where the value was created (this removes it from the VM stack)
|
||||
self.insertions.insert(pushed_at, SetLocal(next_local_id.0));
|
||||
self.vm_stack.remove(found_index);
|
||||
|
||||
// Insert a GetLocal at the current position
|
||||
let inst = GetLocal(next_local_id.0);
|
||||
if DEBUG_LOG {
|
||||
println!(
|
||||
"{:?} {:?} (& insert {:?} at {:?})",
|
||||
inst,
|
||||
self.vm_stack,
|
||||
SetLocal(next_local_id.0),
|
||||
pushed_at
|
||||
);
|
||||
}
|
||||
self.code.push(inst);
|
||||
self.vm_stack.push(symbol);
|
||||
|
||||
// This Symbol is no longer stored in the VM stack, but in a local
|
||||
None
|
||||
} else {
|
||||
panic!(
|
||||
"{:?} has state {:?} but not found in VM stack",
|
||||
symbol, vm_state
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popped { pushed_at } => {
|
||||
// This Symbol is being used for a second time
|
||||
|
||||
// Insert a TeeLocal where it was created (must remain on the stack for the first usage)
|
||||
self.insertions.insert(pushed_at, TeeLocal(next_local_id.0));
|
||||
|
||||
// Insert a GetLocal at the current position
|
||||
let inst = GetLocal(next_local_id.0);
|
||||
if DEBUG_LOG {
|
||||
println!(
|
||||
"{:?} {:?} (& insert {:?} at {:?})",
|
||||
inst,
|
||||
self.vm_stack,
|
||||
TeeLocal(next_local_id.0),
|
||||
pushed_at
|
||||
);
|
||||
}
|
||||
self.code.push(inst);
|
||||
self.vm_stack.push(symbol);
|
||||
|
||||
// This symbol has been promoted to a Local
|
||||
// Tell the caller it no longer has a VirtualMachineSymbolState
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) {
|
||||
match inst {
|
||||
Unreachable => (0, false),
|
||||
Nop => (0, false),
|
||||
Block(_) => (0, false),
|
||||
Loop(_) => (0, false),
|
||||
If(_) => (1, false),
|
||||
Else => (0, false),
|
||||
End => (0, false),
|
||||
Br(_) => (0, false),
|
||||
BrIf(_) => (1, false),
|
||||
BrTable(_) => (1, false),
|
||||
Return => (0, false),
|
||||
|
||||
Call(_) | CallIndirect(_, _) => {
|
||||
panic!("Unknown number of pushes and pops. Use add_call()");
|
||||
}
|
||||
|
||||
Drop => (1, false),
|
||||
Select => (3, true),
|
||||
|
||||
GetLocal(_) => (0, true),
|
||||
SetLocal(_) => (1, false),
|
||||
TeeLocal(_) => (1, true),
|
||||
GetGlobal(_) => (0, true),
|
||||
SetGlobal(_) => (1, false),
|
||||
|
||||
I32Load(_, _) => (1, true),
|
||||
I64Load(_, _) => (1, true),
|
||||
F32Load(_, _) => (1, true),
|
||||
F64Load(_, _) => (1, true),
|
||||
I32Load8S(_, _) => (1, true),
|
||||
I32Load8U(_, _) => (1, true),
|
||||
I32Load16S(_, _) => (1, true),
|
||||
I32Load16U(_, _) => (1, true),
|
||||
I64Load8S(_, _) => (1, true),
|
||||
I64Load8U(_, _) => (1, true),
|
||||
I64Load16S(_, _) => (1, true),
|
||||
I64Load16U(_, _) => (1, true),
|
||||
I64Load32S(_, _) => (1, true),
|
||||
I64Load32U(_, _) => (1, true),
|
||||
I32Store(_, _) => (2, false),
|
||||
I64Store(_, _) => (2, false),
|
||||
F32Store(_, _) => (2, false),
|
||||
F64Store(_, _) => (2, false),
|
||||
I32Store8(_, _) => (2, false),
|
||||
I32Store16(_, _) => (2, false),
|
||||
I64Store8(_, _) => (2, false),
|
||||
I64Store16(_, _) => (2, false),
|
||||
I64Store32(_, _) => (2, false),
|
||||
|
||||
CurrentMemory(_) => (0, true),
|
||||
GrowMemory(_) => (1, true),
|
||||
I32Const(_) => (0, true),
|
||||
I64Const(_) => (0, true),
|
||||
F32Const(_) => (0, true),
|
||||
F64Const(_) => (0, true),
|
||||
|
||||
I32Eqz => (1, true),
|
||||
I32Eq => (2, true),
|
||||
I32Ne => (2, true),
|
||||
I32LtS => (2, true),
|
||||
I32LtU => (2, true),
|
||||
I32GtS => (2, true),
|
||||
I32GtU => (2, true),
|
||||
I32LeS => (2, true),
|
||||
I32LeU => (2, true),
|
||||
I32GeS => (2, true),
|
||||
I32GeU => (2, true),
|
||||
|
||||
I64Eqz => (1, true),
|
||||
I64Eq => (2, true),
|
||||
I64Ne => (2, true),
|
||||
I64LtS => (2, true),
|
||||
I64LtU => (2, true),
|
||||
I64GtS => (2, true),
|
||||
I64GtU => (2, true),
|
||||
I64LeS => (2, true),
|
||||
I64LeU => (2, true),
|
||||
I64GeS => (2, true),
|
||||
I64GeU => (2, true),
|
||||
|
||||
F32Eq => (2, true),
|
||||
F32Ne => (2, true),
|
||||
F32Lt => (2, true),
|
||||
F32Gt => (2, true),
|
||||
F32Le => (2, true),
|
||||
F32Ge => (2, true),
|
||||
|
||||
F64Eq => (2, true),
|
||||
F64Ne => (2, true),
|
||||
F64Lt => (2, true),
|
||||
F64Gt => (2, true),
|
||||
F64Le => (2, true),
|
||||
F64Ge => (2, true),
|
||||
|
||||
I32Clz => (1, true),
|
||||
I32Ctz => (1, true),
|
||||
I32Popcnt => (1, true),
|
||||
I32Add => (2, true),
|
||||
I32Sub => (2, true),
|
||||
I32Mul => (2, true),
|
||||
I32DivS => (2, true),
|
||||
I32DivU => (2, true),
|
||||
I32RemS => (2, true),
|
||||
I32RemU => (2, true),
|
||||
I32And => (2, true),
|
||||
I32Or => (2, true),
|
||||
I32Xor => (2, true),
|
||||
I32Shl => (2, true),
|
||||
I32ShrS => (2, true),
|
||||
I32ShrU => (2, true),
|
||||
I32Rotl => (2, true),
|
||||
I32Rotr => (2, true),
|
||||
|
||||
I64Clz => (1, true),
|
||||
I64Ctz => (1, true),
|
||||
I64Popcnt => (1, true),
|
||||
I64Add => (2, true),
|
||||
I64Sub => (2, true),
|
||||
I64Mul => (2, true),
|
||||
I64DivS => (2, true),
|
||||
I64DivU => (2, true),
|
||||
I64RemS => (2, true),
|
||||
I64RemU => (2, true),
|
||||
I64And => (2, true),
|
||||
I64Or => (2, true),
|
||||
I64Xor => (2, true),
|
||||
I64Shl => (2, true),
|
||||
I64ShrS => (2, true),
|
||||
I64ShrU => (2, true),
|
||||
I64Rotl => (2, true),
|
||||
I64Rotr => (2, true),
|
||||
|
||||
F32Abs => (1, true),
|
||||
F32Neg => (1, true),
|
||||
F32Ceil => (1, true),
|
||||
F32Floor => (1, true),
|
||||
F32Trunc => (1, true),
|
||||
F32Nearest => (1, true),
|
||||
F32Sqrt => (1, true),
|
||||
F32Add => (2, true),
|
||||
F32Sub => (2, true),
|
||||
F32Mul => (2, true),
|
||||
F32Div => (2, true),
|
||||
F32Min => (2, true),
|
||||
F32Max => (2, true),
|
||||
F32Copysign => (2, true),
|
||||
|
||||
F64Abs => (1, true),
|
||||
F64Neg => (1, true),
|
||||
F64Ceil => (1, true),
|
||||
F64Floor => (1, true),
|
||||
F64Trunc => (1, true),
|
||||
F64Nearest => (1, true),
|
||||
F64Sqrt => (1, true),
|
||||
F64Add => (2, true),
|
||||
F64Sub => (2, true),
|
||||
F64Mul => (2, true),
|
||||
F64Div => (2, true),
|
||||
F64Min => (2, true),
|
||||
F64Max => (2, true),
|
||||
F64Copysign => (2, true),
|
||||
|
||||
I32WrapI64 => (1, true),
|
||||
I32TruncSF32 => (1, true),
|
||||
I32TruncUF32 => (1, true),
|
||||
I32TruncSF64 => (1, true),
|
||||
I32TruncUF64 => (1, true),
|
||||
I64ExtendSI32 => (1, true),
|
||||
I64ExtendUI32 => (1, true),
|
||||
I64TruncSF32 => (1, true),
|
||||
I64TruncUF32 => (1, true),
|
||||
I64TruncSF64 => (1, true),
|
||||
I64TruncUF64 => (1, true),
|
||||
F32ConvertSI32 => (1, true),
|
||||
F32ConvertUI32 => (1, true),
|
||||
F32ConvertSI64 => (1, true),
|
||||
F32ConvertUI64 => (1, true),
|
||||
F32DemoteF64 => (1, true),
|
||||
F64ConvertSI32 => (1, true),
|
||||
F64ConvertUI32 => (1, true),
|
||||
F64ConvertSI64 => (1, true),
|
||||
F64ConvertUI64 => (1, true),
|
||||
F64PromoteF32 => (1, true),
|
||||
|
||||
I32ReinterpretF32 => (1, true),
|
||||
I64ReinterpretF64 => (1, true),
|
||||
F32ReinterpretI32 => (1, true),
|
||||
F64ReinterpretI64 => (1, true),
|
||||
}
|
||||
}
|
|
@ -6,9 +6,9 @@ use crate::{PTR_SIZE, PTR_TYPE};
|
|||
// See README for background information on Wasm locals, memory and function calls
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WasmLayout {
|
||||
// Primitive number value. Just a Wasm local, without any stack memory.
|
||||
// For example, Roc i8 is represented as Wasm i32. Store the type and the original size.
|
||||
LocalOnly(ValueType, u32),
|
||||
// Primitive number value, without any stack memory.
|
||||
// For example, Roc i8 is represented as Primitive(ValueType::I32, 1)
|
||||
Primitive(ValueType, u32),
|
||||
|
||||
// Local pointer to stack memory
|
||||
StackMemory { size: u32, alignment_bytes: u32 },
|
||||
|
@ -27,13 +27,13 @@ impl WasmLayout {
|
|||
let alignment_bytes = layout.alignment_bytes(PTR_SIZE);
|
||||
|
||||
match layout {
|
||||
Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size),
|
||||
Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::Primitive(I32, size),
|
||||
|
||||
Layout::Builtin(Int64) => Self::LocalOnly(I64, size),
|
||||
Layout::Builtin(Int64) => Self::Primitive(I64, size),
|
||||
|
||||
Layout::Builtin(Float32) => Self::LocalOnly(F32, size),
|
||||
Layout::Builtin(Float32) => Self::Primitive(F32, size),
|
||||
|
||||
Layout::Builtin(Float64) => Self::LocalOnly(F64, size),
|
||||
Layout::Builtin(Float64) => Self::Primitive(F64, size),
|
||||
|
||||
Layout::Builtin(
|
||||
Int128
|
||||
|
@ -67,7 +67,7 @@ impl WasmLayout {
|
|||
|
||||
pub fn value_type(&self) -> ValueType {
|
||||
match self {
|
||||
Self::LocalOnly(type_, _) => *type_,
|
||||
Self::Primitive(type_, _) => *type_,
|
||||
_ => PTR_TYPE,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
mod backend;
|
||||
mod code_builder;
|
||||
pub mod from_wasm32_memory;
|
||||
mod layout;
|
||||
mod storage;
|
||||
|
@ -13,6 +14,7 @@ use roc_mono::ir::{Proc, ProcLayout};
|
|||
use roc_mono::layout::LayoutIds;
|
||||
|
||||
use crate::backend::WasmBackend;
|
||||
use crate::code_builder::CodeBuilder;
|
||||
|
||||
const PTR_SIZE: u32 = 4;
|
||||
const PTR_TYPE: ValueType = ValueType::I32;
|
||||
|
@ -79,7 +81,7 @@ pub fn build_module_help<'a>(
|
|||
.with_internal(Internal::Function(function_index))
|
||||
.build();
|
||||
|
||||
backend.builder.push_export(export);
|
||||
backend.module_builder.push_export(export);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,21 +96,21 @@ pub fn build_module_help<'a>(
|
|||
let memory = builder::MemoryBuilder::new()
|
||||
.with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB)
|
||||
.build();
|
||||
backend.builder.push_memory(memory);
|
||||
backend.module_builder.push_memory(memory);
|
||||
let memory_export = builder::export()
|
||||
.field("memory")
|
||||
.with_internal(Internal::Memory(0))
|
||||
.build();
|
||||
backend.builder.push_export(memory_export);
|
||||
backend.module_builder.push_export(memory_export);
|
||||
|
||||
let stack_pointer_global = builder::global()
|
||||
.with_type(PTR_TYPE)
|
||||
.mutable()
|
||||
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
|
||||
.build();
|
||||
backend.builder.push_global(stack_pointer_global);
|
||||
backend.module_builder.push_global(stack_pointer_global);
|
||||
|
||||
Ok((backend.builder, main_function_index))
|
||||
Ok((backend.module_builder, main_function_index))
|
||||
}
|
||||
|
||||
fn encode_alignment(bytes: u32) -> u32 {
|
||||
|
@ -130,34 +132,45 @@ pub struct CopyMemoryConfig {
|
|||
alignment_bytes: u32,
|
||||
}
|
||||
|
||||
pub fn copy_memory(instructions: &mut Vec<Instruction>, config: CopyMemoryConfig) {
|
||||
pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
|
||||
if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset {
|
||||
return;
|
||||
}
|
||||
|
||||
let alignment_flag = encode_alignment(config.alignment_bytes);
|
||||
let mut i = 0;
|
||||
while config.size - i >= 8 {
|
||||
instructions.push(GetLocal(config.to_ptr.0));
|
||||
instructions.push(GetLocal(config.from_ptr.0));
|
||||
instructions.push(I64Load(alignment_flag, i + config.from_offset));
|
||||
instructions.push(I64Store(alignment_flag, i + config.to_offset));
|
||||
code_builder.extend_from_slice(&[
|
||||
GetLocal(config.to_ptr.0),
|
||||
GetLocal(config.from_ptr.0),
|
||||
I64Load(alignment_flag, i + config.from_offset),
|
||||
I64Store(alignment_flag, i + config.to_offset),
|
||||
]);
|
||||
i += 8;
|
||||
}
|
||||
if config.size - i >= 4 {
|
||||
instructions.push(GetLocal(config.to_ptr.0));
|
||||
instructions.push(GetLocal(config.from_ptr.0));
|
||||
instructions.push(I32Load(alignment_flag, i + config.from_offset));
|
||||
instructions.push(I32Store(alignment_flag, i + config.to_offset));
|
||||
code_builder.extend_from_slice(&[
|
||||
GetLocal(config.to_ptr.0),
|
||||
GetLocal(config.from_ptr.0),
|
||||
I32Load(alignment_flag, i + config.from_offset),
|
||||
I32Store(alignment_flag, i + config.to_offset),
|
||||
]);
|
||||
i += 4;
|
||||
}
|
||||
while config.size - i > 0 {
|
||||
instructions.push(GetLocal(config.to_ptr.0));
|
||||
instructions.push(GetLocal(config.from_ptr.0));
|
||||
instructions.push(I32Load8U(alignment_flag, i + config.from_offset));
|
||||
instructions.push(I32Store8(alignment_flag, i + config.to_offset));
|
||||
code_builder.extend_from_slice(&[
|
||||
GetLocal(config.to_ptr.0),
|
||||
GetLocal(config.from_ptr.0),
|
||||
I32Load8U(alignment_flag, i + config.from_offset),
|
||||
I32Store8(alignment_flag, i + config.to_offset),
|
||||
]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Round up to alignment_bytes (assumed to be a power of 2)
|
||||
/// Round up to alignment_bytes (which must be a power of 2)
|
||||
pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
|
||||
debug_assert!(alignment_bytes.count_ones() == 1);
|
||||
let mut aligned = unaligned;
|
||||
aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary
|
||||
aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
use crate::{copy_memory, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8};
|
||||
use parity_wasm::elements::{Instruction, Instruction::*, ValueType};
|
||||
use parity_wasm::elements::{Instruction::*, ValueType};
|
||||
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState};
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::{
|
||||
copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4,
|
||||
ALIGN_8, PTR_SIZE, PTR_TYPE,
|
||||
};
|
||||
|
||||
pub enum StoredValueKind {
|
||||
Parameter,
|
||||
Variable,
|
||||
ReturnValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StackMemoryLocation {
|
||||
|
@ -17,98 +32,268 @@ impl StackMemoryLocation {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SymbolStorage {
|
||||
// TODO: implicit storage in the VM stack
|
||||
// TODO: const data storage
|
||||
pub enum StoredValue {
|
||||
/// A value stored implicitly in the VM stack (primitives only)
|
||||
VirtualMachineStack {
|
||||
vm_state: VirtualMachineSymbolState,
|
||||
value_type: ValueType,
|
||||
size: u32,
|
||||
},
|
||||
|
||||
/// A local variable in the Wasm function (primitives only)
|
||||
Local {
|
||||
local_id: LocalId,
|
||||
value_type: ValueType,
|
||||
size: u32,
|
||||
},
|
||||
|
||||
/// A Struct, or other non-primitive value, stored in stack memory
|
||||
StackMemory {
|
||||
location: StackMemoryLocation,
|
||||
size: u32,
|
||||
alignment_bytes: u32,
|
||||
},
|
||||
// TODO: const data storage (fixed address)
|
||||
}
|
||||
|
||||
impl SymbolStorage {
|
||||
/// generate code to copy from another storage of the same type
|
||||
pub fn copy_from(
|
||||
&self,
|
||||
from: &Self,
|
||||
instructions: &mut Vec<Instruction>,
|
||||
stack_frame_pointer: Option<LocalId>,
|
||||
) {
|
||||
match (self, from) {
|
||||
(
|
||||
Self::Local {
|
||||
local_id: to_local_id,
|
||||
value_type: to_value_type,
|
||||
size: to_size,
|
||||
},
|
||||
Self::Local {
|
||||
local_id: from_local_id,
|
||||
value_type: from_value_type,
|
||||
size: from_size,
|
||||
},
|
||||
) => {
|
||||
debug_assert!(to_value_type == from_value_type);
|
||||
debug_assert!(to_size == from_size);
|
||||
instructions.push(GetLocal(from_local_id.0));
|
||||
instructions.push(SetLocal(to_local_id.0));
|
||||
/// Helper structure for WasmBackend, to keep track of how values are stored,
|
||||
/// including the VM stack, local variables, and linear memory
|
||||
pub struct Storage {
|
||||
pub arg_types: std::vec::Vec<ValueType>,
|
||||
pub local_types: std::vec::Vec<ValueType>,
|
||||
pub symbol_storage_map: MutMap<Symbol, StoredValue>,
|
||||
pub stack_frame_pointer: Option<LocalId>,
|
||||
pub stack_frame_size: i32,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub fn new() -> Self {
|
||||
Storage {
|
||||
arg_types: std::vec::Vec::with_capacity(8),
|
||||
local_types: std::vec::Vec::with_capacity(32),
|
||||
symbol_storage_map: MutMap::default(),
|
||||
stack_frame_pointer: None,
|
||||
stack_frame_size: 0,
|
||||
}
|
||||
(
|
||||
Self::StackMemory {
|
||||
location: to_location,
|
||||
size: to_size,
|
||||
alignment_bytes: to_alignment_bytes,
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.arg_types.clear();
|
||||
self.local_types.clear();
|
||||
self.symbol_storage_map.clear();
|
||||
self.stack_frame_pointer = None;
|
||||
self.stack_frame_size = 0;
|
||||
}
|
||||
|
||||
/// Internal use only. If you think you want it externally, you really want `allocate`
|
||||
fn get_next_local_id(&self) -> LocalId {
|
||||
LocalId((self.arg_types.len() + self.local_types.len()) as u32)
|
||||
}
|
||||
|
||||
/// Allocate storage for a Roc value
|
||||
///
|
||||
/// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack.
|
||||
/// This is really just a way to model how the stack machine works as a sort of
|
||||
/// temporary storage. It doesn't result in any code generation.
|
||||
/// For some values, this initial storage allocation may need to be upgraded later
|
||||
/// to a Local. See `load_symbols`.
|
||||
///
|
||||
/// Structs and Tags are stored in memory rather than in Wasm primitives.
|
||||
/// They are allocated a certain offset and size in the stack frame.
|
||||
pub fn allocate(
|
||||
&mut self,
|
||||
wasm_layout: &WasmLayout,
|
||||
symbol: Symbol,
|
||||
kind: StoredValueKind,
|
||||
) -> StoredValue {
|
||||
let next_local_id = self.get_next_local_id();
|
||||
|
||||
let storage = match wasm_layout {
|
||||
WasmLayout::Primitive(value_type, size) => match kind {
|
||||
StoredValueKind::Parameter => {
|
||||
self.arg_types.push(*value_type);
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type: *value_type,
|
||||
size: *size,
|
||||
}
|
||||
}
|
||||
_ => StoredValue::VirtualMachineStack {
|
||||
vm_state: VirtualMachineSymbolState::NotYetPushed,
|
||||
value_type: *value_type,
|
||||
size: *size,
|
||||
},
|
||||
Self::StackMemory {
|
||||
location: from_location,
|
||||
size: from_size,
|
||||
alignment_bytes: from_alignment_bytes,
|
||||
},
|
||||
) => {
|
||||
let (from_ptr, from_offset) = from_location.local_and_offset(stack_frame_pointer);
|
||||
let (to_ptr, to_offset) = to_location.local_and_offset(stack_frame_pointer);
|
||||
debug_assert!(*to_size == *from_size);
|
||||
debug_assert!(*to_alignment_bytes == *from_alignment_bytes);
|
||||
|
||||
WasmLayout::HeapMemory => {
|
||||
match kind {
|
||||
StoredValueKind::Parameter => self.arg_types.push(PTR_TYPE),
|
||||
_ => self.local_types.push(PTR_TYPE),
|
||||
}
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type: PTR_TYPE,
|
||||
size: PTR_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
WasmLayout::StackMemory {
|
||||
size,
|
||||
alignment_bytes,
|
||||
} => {
|
||||
let location = match kind {
|
||||
StoredValueKind::Parameter => {
|
||||
self.arg_types.push(PTR_TYPE);
|
||||
StackMemoryLocation::PointerArg(next_local_id)
|
||||
}
|
||||
|
||||
StoredValueKind::Variable => {
|
||||
if self.stack_frame_pointer.is_none() {
|
||||
self.stack_frame_pointer = Some(next_local_id);
|
||||
self.local_types.push(PTR_TYPE);
|
||||
}
|
||||
|
||||
let offset =
|
||||
round_up_to_alignment(self.stack_frame_size, *alignment_bytes as i32);
|
||||
|
||||
self.stack_frame_size = offset + (*size as i32);
|
||||
|
||||
StackMemoryLocation::FrameOffset(offset as u32)
|
||||
}
|
||||
|
||||
StoredValueKind::ReturnValue => StackMemoryLocation::PointerArg(LocalId(0)),
|
||||
};
|
||||
|
||||
StoredValue::StackMemory {
|
||||
location,
|
||||
size: *size,
|
||||
alignment_bytes: *alignment_bytes,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.symbol_storage_map.insert(symbol, storage.clone());
|
||||
|
||||
storage
|
||||
}
|
||||
|
||||
/// Get storage info for a given symbol
|
||||
pub fn get(&self, sym: &Symbol) -> &StoredValue {
|
||||
self.symbol_storage_map.get(sym).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Symbol {:?} not found in function scope:\n{:?}",
|
||||
sym, self.symbol_storage_map
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Load symbols to the top of the VM stack
|
||||
/// Avoid calling this method in a loop with one symbol at a time! It will work,
|
||||
/// but it generates very inefficient Wasm code.
|
||||
pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) {
|
||||
if code_builder.verify_stack_match(symbols) {
|
||||
// The symbols were already at the top of the stack, do nothing!
|
||||
// This should be quite common due to the structure of the Mono IR
|
||||
return;
|
||||
}
|
||||
for sym in symbols.iter() {
|
||||
let storage = self.get(sym).to_owned();
|
||||
match storage {
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state,
|
||||
value_type,
|
||||
size,
|
||||
} => {
|
||||
let next_local_id = self.get_next_local_id();
|
||||
let maybe_next_vm_state =
|
||||
code_builder.load_symbol(*sym, vm_state, next_local_id);
|
||||
match maybe_next_vm_state {
|
||||
// The act of loading the value changed the VM state, so update it
|
||||
Some(next_vm_state) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state: next_vm_state,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// Loading the value required creating a new local, because
|
||||
// it was not in a convenient position in the VM stack.
|
||||
self.local_types.push(value_type);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
StoredValue::Local { local_id, .. }
|
||||
| StoredValue::StackMemory {
|
||||
location: StackMemoryLocation::PointerArg(local_id),
|
||||
..
|
||||
} => {
|
||||
code_builder.push(GetLocal(local_id.0));
|
||||
code_builder.set_top_symbol(*sym);
|
||||
}
|
||||
|
||||
StoredValue::StackMemory {
|
||||
location: StackMemoryLocation::FrameOffset(offset),
|
||||
..
|
||||
} => {
|
||||
code_builder.extend_from_slice(&[
|
||||
GetLocal(self.stack_frame_pointer.unwrap().0),
|
||||
I32Const(offset as i32),
|
||||
I32Add,
|
||||
]);
|
||||
code_builder.set_top_symbol(*sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code to copy a StoredValue to an arbitrary memory location
|
||||
/// (defined by a pointer and offset).
|
||||
pub fn copy_value_to_memory(
|
||||
&mut self,
|
||||
code_builder: &mut CodeBuilder,
|
||||
to_ptr: LocalId,
|
||||
to_offset: u32,
|
||||
from_symbol: Symbol,
|
||||
) -> u32 {
|
||||
let from_storage = self.get(&from_symbol).to_owned();
|
||||
match from_storage {
|
||||
StoredValue::StackMemory {
|
||||
location,
|
||||
size,
|
||||
alignment_bytes,
|
||||
} => {
|
||||
let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||
copy_memory(
|
||||
instructions,
|
||||
code_builder,
|
||||
CopyMemoryConfig {
|
||||
from_ptr,
|
||||
from_offset,
|
||||
to_ptr,
|
||||
to_offset,
|
||||
size: *from_size,
|
||||
alignment_bytes: *from_alignment_bytes,
|
||||
size,
|
||||
alignment_bytes,
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"Cannot copy different storage types {:?} to {:?}",
|
||||
from, self
|
||||
);
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
/// Generate code to copy to a memory address (such as a struct index)
|
||||
pub fn copy_to_memory(
|
||||
&self,
|
||||
instructions: &mut Vec<Instruction>,
|
||||
to_ptr: LocalId,
|
||||
to_offset: u32,
|
||||
stack_frame_pointer: Option<LocalId>,
|
||||
) -> u32 {
|
||||
match self {
|
||||
Self::Local {
|
||||
local_id,
|
||||
value_type,
|
||||
size,
|
||||
..
|
||||
StoredValue::VirtualMachineStack {
|
||||
value_type, size, ..
|
||||
}
|
||||
| StoredValue::Local {
|
||||
value_type, size, ..
|
||||
} => {
|
||||
let store_instruction = match (value_type, size) {
|
||||
(ValueType::I64, 8) => I64Store(ALIGN_8, to_offset),
|
||||
|
@ -121,31 +306,133 @@ impl SymbolStorage {
|
|||
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
|
||||
}
|
||||
};
|
||||
instructions.push(GetLocal(to_ptr.0));
|
||||
instructions.push(GetLocal(local_id.0));
|
||||
instructions.push(store_instruction);
|
||||
*size
|
||||
code_builder.push(GetLocal(to_ptr.0));
|
||||
self.load_symbols(code_builder, &[from_symbol]);
|
||||
code_builder.push(store_instruction);
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::StackMemory {
|
||||
location,
|
||||
size,
|
||||
alignment_bytes,
|
||||
} => {
|
||||
let (from_ptr, from_offset) = location.local_and_offset(stack_frame_pointer);
|
||||
/// Generate code to copy from one StoredValue to another
|
||||
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
|
||||
pub fn clone_value(
|
||||
&mut self,
|
||||
code_builder: &mut CodeBuilder,
|
||||
to: &StoredValue,
|
||||
from: &StoredValue,
|
||||
from_symbol: Symbol,
|
||||
) {
|
||||
use StoredValue::*;
|
||||
|
||||
match (to, from) {
|
||||
(
|
||||
Local {
|
||||
local_id: to_local_id,
|
||||
value_type: to_value_type,
|
||||
size: to_size,
|
||||
},
|
||||
VirtualMachineStack {
|
||||
value_type: from_value_type,
|
||||
size: from_size,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
debug_assert!(to_value_type == from_value_type);
|
||||
debug_assert!(to_size == from_size);
|
||||
self.load_symbols(code_builder, &[from_symbol]);
|
||||
code_builder.push(SetLocal(to_local_id.0));
|
||||
self.symbol_storage_map.insert(from_symbol, to.clone());
|
||||
}
|
||||
|
||||
(
|
||||
Local {
|
||||
local_id: to_local_id,
|
||||
value_type: to_value_type,
|
||||
size: to_size,
|
||||
},
|
||||
Local {
|
||||
local_id: from_local_id,
|
||||
value_type: from_value_type,
|
||||
size: from_size,
|
||||
},
|
||||
) => {
|
||||
debug_assert!(to_value_type == from_value_type);
|
||||
debug_assert!(to_size == from_size);
|
||||
code_builder
|
||||
.extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]);
|
||||
}
|
||||
|
||||
(
|
||||
StackMemory {
|
||||
location: to_location,
|
||||
size: to_size,
|
||||
alignment_bytes: to_alignment_bytes,
|
||||
},
|
||||
StackMemory {
|
||||
location: from_location,
|
||||
size: from_size,
|
||||
alignment_bytes: from_alignment_bytes,
|
||||
},
|
||||
) => {
|
||||
let (from_ptr, from_offset) =
|
||||
from_location.local_and_offset(self.stack_frame_pointer);
|
||||
let (to_ptr, to_offset) = to_location.local_and_offset(self.stack_frame_pointer);
|
||||
debug_assert!(*to_size == *from_size);
|
||||
debug_assert!(*to_alignment_bytes == *from_alignment_bytes);
|
||||
copy_memory(
|
||||
instructions,
|
||||
code_builder,
|
||||
CopyMemoryConfig {
|
||||
from_ptr,
|
||||
from_offset,
|
||||
to_ptr,
|
||||
to_offset,
|
||||
size: *size,
|
||||
alignment_bytes: *alignment_bytes,
|
||||
size: *from_size,
|
||||
alignment_bytes: *from_alignment_bytes,
|
||||
},
|
||||
);
|
||||
*size
|
||||
}
|
||||
|
||||
_ => {
|
||||
panic!("Cannot copy storage from {:?} to {:?}", from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure a StoredValue has an associated local
|
||||
/// This is useful when a value needs to be accessed from a more deeply-nested block.
|
||||
/// In that case we want to make sure it's not just stored in the VM stack, because
|
||||
/// blocks can't access the VM stack from outer blocks, but they can access locals.
|
||||
/// (In the case of structs in stack memory, we just use the stack frame pointer local)
|
||||
pub fn ensure_value_has_local(
|
||||
&mut self,
|
||||
code_builder: &mut CodeBuilder,
|
||||
symbol: Symbol,
|
||||
storage: StoredValue,
|
||||
) -> StoredValue {
|
||||
if let StoredValue::VirtualMachineStack {
|
||||
vm_state,
|
||||
value_type,
|
||||
size,
|
||||
} = storage
|
||||
{
|
||||
let local_id = self.get_next_local_id();
|
||||
if vm_state != VirtualMachineSymbolState::NotYetPushed {
|
||||
code_builder.load_symbol(symbol, vm_state, local_id);
|
||||
code_builder.push(SetLocal(local_id.0));
|
||||
}
|
||||
|
||||
self.local_types.push(value_type);
|
||||
let new_storage = StoredValue::Local {
|
||||
local_id,
|
||||
value_type,
|
||||
size,
|
||||
};
|
||||
|
||||
self.symbol_storage_map.insert(symbol, new_storage.clone());
|
||||
new_storage
|
||||
} else {
|
||||
storage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
compiler/gen_wasm/test-compare.sh
Executable file
30
compiler/gen_wasm/test-compare.sh
Executable file
|
@ -0,0 +1,30 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ -z "$1" || -z "$2" ]]
|
||||
then
|
||||
echo "$0 needs 2 arguments: the directories to compare"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
OVERHEAD_BYTES=114 # total file size minus generated code size (test wrapper + module headers)
|
||||
|
||||
|
||||
printf "filename \tLHS\tRHS\tchange\n"
|
||||
printf "======== \t===\t===\t======\n"
|
||||
|
||||
for f in `ls $1/wasm`
|
||||
do
|
||||
if [[ ! -f "$2/wasm/$f" ]]
|
||||
then
|
||||
echo "$f found in $1/wasm but not in $2/wasm"
|
||||
continue
|
||||
fi
|
||||
SIZE1=$(stat --format '%s' "$1/wasm/$f")
|
||||
SIZE2=$(stat --format '%s' "$2/wasm/$f")
|
||||
CHANGE=$(( $SIZE2 - $SIZE1 ))
|
||||
NET_SIZE1=$(( $SIZE1 - $OVERHEAD_BYTES ))
|
||||
NET_SIZE2=$(( $SIZE2 - $OVERHEAD_BYTES ))
|
||||
PERCENT_CHANGE=$(( $CHANGE * 100 / $NET_SIZE1 ))
|
||||
printf "%s\t%d\t%d\t%d\t%d%%\n" $f $NET_SIZE1 $NET_SIZE2 $CHANGE $PERCENT_CHANGE
|
||||
done
|
24
compiler/gen_wasm/test-run.sh
Executable file
24
compiler/gen_wasm/test-run.sh
Executable file
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
|
||||
TARGET_DIR=$1
|
||||
|
||||
if [[ -z "$TARGET_DIR" ]]
|
||||
then
|
||||
echo "$0 needs an argument: target directory for output wasm and wat files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf output $TARGET_DIR
|
||||
mkdir -p output $TARGET_DIR $TARGET_DIR/wasm $TARGET_DIR/wat
|
||||
cargo test -- --test-threads=1 --nocapture
|
||||
|
||||
mv output/* $TARGET_DIR/wasm
|
||||
|
||||
for f in `ls $TARGET_DIR/wasm`
|
||||
do
|
||||
wasm2wat $TARGET_DIR/wasm/$f -o $TARGET_DIR/wat/${f%.wasm}.wat
|
||||
done
|
||||
|
||||
|
||||
SIZE=$(du -b "$TARGET_DIR/wasm")
|
||||
echo "Total bytes *.wasm = $SIZE"
|
|
@ -1,3 +1,7 @@
|
|||
use std::cell::Cell;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
// use roc_std::{RocDec, RocList, RocOrder, RocStr};
|
||||
|
@ -6,6 +10,10 @@ use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
|
|||
|
||||
const TEST_WRAPPER_NAME: &str = "test_wrapper";
|
||||
|
||||
std::thread_local! {
|
||||
static TEST_COUNTER: Cell<u32> = Cell::new(0);
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
||||
|
@ -103,7 +111,18 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
// for debugging (e.g. with wasm2wat)
|
||||
if false {
|
||||
use std::io::Write;
|
||||
let path = "/home/brian/Documents/roc/compiler/gen_wasm/debug.wasm";
|
||||
|
||||
let mut hash_state = DefaultHasher::new();
|
||||
src.hash(&mut hash_state);
|
||||
let src_hash = hash_state.finish();
|
||||
|
||||
// Filename contains a hash of the Roc test source code. Helpful when comparing across commits.
|
||||
let dir = "/tmp/roc/compiler/gen_wasm/output";
|
||||
std::fs::create_dir_all(dir).unwrap();
|
||||
let path = format!("{}/test-{:016x}.wasm", dir, src_hash);
|
||||
|
||||
// Print out filename (appears just after test name)
|
||||
println!("dumping file {:?}", path);
|
||||
|
||||
match std::fs::File::create(path) {
|
||||
Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e),
|
||||
|
|
|
@ -641,19 +641,6 @@ mod wasm_records {
|
|||
// );
|
||||
// }
|
||||
|
||||
#[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!(
|
||||
|
|
|
@ -71,6 +71,7 @@ pub enum LowLevel {
|
|||
NumLte,
|
||||
NumCompare,
|
||||
NumDivUnchecked,
|
||||
NumDivCeilUnchecked,
|
||||
NumRemUnchecked,
|
||||
NumIsMultipleOf,
|
||||
NumAbs,
|
||||
|
@ -107,6 +108,119 @@ pub enum LowLevel {
|
|||
ExpectTrue,
|
||||
}
|
||||
|
||||
macro_rules! first_order {
|
||||
() => {
|
||||
StrConcat
|
||||
| StrJoinWith
|
||||
| StrIsEmpty
|
||||
| StrStartsWith
|
||||
| StrStartsWithCodePt
|
||||
| StrEndsWith
|
||||
| StrSplit
|
||||
| StrCountGraphemes
|
||||
| StrFromInt
|
||||
| StrFromUtf8
|
||||
| StrFromUtf8Range
|
||||
| StrToUtf8
|
||||
| StrRepeat
|
||||
| StrFromFloat
|
||||
| ListLen
|
||||
| ListGetUnsafe
|
||||
| ListSet
|
||||
| ListDrop
|
||||
| ListDropAt
|
||||
| ListSingle
|
||||
| ListRepeat
|
||||
| ListReverse
|
||||
| ListConcat
|
||||
| ListContains
|
||||
| ListAppend
|
||||
| ListPrepend
|
||||
| ListJoin
|
||||
| ListRange
|
||||
| ListSwap
|
||||
| DictSize
|
||||
| DictEmpty
|
||||
| DictInsert
|
||||
| DictRemove
|
||||
| DictContains
|
||||
| DictGetUnsafe
|
||||
| DictKeys
|
||||
| DictValues
|
||||
| DictUnion
|
||||
| DictIntersection
|
||||
| DictDifference
|
||||
| SetFromList
|
||||
| NumAdd
|
||||
| NumAddWrap
|
||||
| NumAddChecked
|
||||
| NumSub
|
||||
| NumSubWrap
|
||||
| NumSubChecked
|
||||
| NumMul
|
||||
| NumMulWrap
|
||||
| NumMulChecked
|
||||
| NumGt
|
||||
| NumGte
|
||||
| NumLt
|
||||
| NumLte
|
||||
| NumCompare
|
||||
| NumDivUnchecked
|
||||
| NumDivCeilUnchecked
|
||||
| NumRemUnchecked
|
||||
| NumIsMultipleOf
|
||||
| NumAbs
|
||||
| NumNeg
|
||||
| NumSin
|
||||
| NumCos
|
||||
| NumSqrtUnchecked
|
||||
| NumLogUnchecked
|
||||
| NumRound
|
||||
| NumToFloat
|
||||
| NumPow
|
||||
| NumCeiling
|
||||
| NumPowInt
|
||||
| NumFloor
|
||||
| NumIsFinite
|
||||
| NumAtan
|
||||
| NumAcos
|
||||
| NumAsin
|
||||
| NumBitwiseAnd
|
||||
| NumBitwiseXor
|
||||
| NumBitwiseOr
|
||||
| NumShiftLeftBy
|
||||
| NumShiftRightBy
|
||||
| NumBytesToU16
|
||||
| NumBytesToU32
|
||||
| NumShiftRightZfBy
|
||||
| NumIntCast
|
||||
| Eq
|
||||
| NotEq
|
||||
| And
|
||||
| Or
|
||||
| Not
|
||||
| Hash
|
||||
| ExpectTrue
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! higher_order {
|
||||
() => {
|
||||
ListMap
|
||||
| ListMap2
|
||||
| ListMap3
|
||||
| ListMapWithIndex
|
||||
| ListKeepIf
|
||||
| ListWalk
|
||||
| ListWalkUntil
|
||||
| ListWalkBackwards
|
||||
| ListKeepOks
|
||||
| ListKeepErrs
|
||||
| ListSortWith
|
||||
| DictWalk
|
||||
};
|
||||
}
|
||||
|
||||
impl LowLevel {
|
||||
/// is one of the arguments always a function?
|
||||
/// An example is List.map.
|
||||
|
@ -114,25 +228,28 @@ impl LowLevel {
|
|||
use LowLevel::*;
|
||||
|
||||
match self {
|
||||
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
|
||||
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8
|
||||
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe
|
||||
| ListSet | ListDrop | ListDropAt | ListSingle | ListRepeat | ListReverse
|
||||
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange
|
||||
| ListSwap | DictSize | DictEmpty | DictInsert | DictRemove | DictContains
|
||||
| DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection
|
||||
| DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub
|
||||
| NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte
|
||||
| NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf
|
||||
| NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
|
||||
| NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan
|
||||
| NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
|
||||
| NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast
|
||||
| Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false,
|
||||
first_order!() => false,
|
||||
higher_order!() => true,
|
||||
}
|
||||
}
|
||||
|
||||
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
|
||||
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
|
||||
| DictWalk => true,
|
||||
pub fn function_argument_position(&self) -> usize {
|
||||
use LowLevel::*;
|
||||
|
||||
match self {
|
||||
first_order!() => unreachable!(),
|
||||
ListMap => 1,
|
||||
ListMap2 => 2,
|
||||
ListMap3 => 3,
|
||||
ListMapWithIndex => 1,
|
||||
ListKeepIf => 1,
|
||||
ListWalk => 2,
|
||||
ListWalkUntil => 2,
|
||||
ListWalkBackwards => 2,
|
||||
ListKeepOks => 1,
|
||||
ListKeepErrs => 1,
|
||||
ListSortWith => 1,
|
||||
DictWalk => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -835,6 +835,9 @@ define_builtins! {
|
|||
|
||||
// used by the dev backend to store the pointer to where to store large return types
|
||||
23 RET_POINTER: "#ret_pointer"
|
||||
|
||||
// used in wasm dev backend to mark values in the VM stack that have no other Symbol
|
||||
24 WASM_ANONYMOUS_STACK_VALUE: "#wasm_anonymous_stack_value"
|
||||
}
|
||||
1 NUM: "Num" => {
|
||||
0 NUM_NUM: "Num" imported // the Num.Num type alias
|
||||
|
@ -943,6 +946,7 @@ define_builtins! {
|
|||
103 NUM_BYTES_TO_U16: "bytesToU16"
|
||||
104 NUM_BYTES_TO_U32: "bytesToU32"
|
||||
105 NUM_CAST_TO_NAT: "#castToNat"
|
||||
106 NUM_DIV_CEIL: "divCeil"
|
||||
}
|
||||
2 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
||||
|
|
|
@ -611,38 +611,25 @@ fn call_spec(
|
|||
op,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
function_name,
|
||||
function_env,
|
||||
..
|
||||
} => {
|
||||
let array = specialization_id.to_bytes();
|
||||
let spec_var = CalleeSpecVar(&array);
|
||||
|
||||
let symbol = {
|
||||
use roc_module::low_level::LowLevel::*;
|
||||
|
||||
match op {
|
||||
ListMap | ListMapWithIndex => call.arguments[1],
|
||||
ListMap2 => call.arguments[2],
|
||||
ListMap3 => call.arguments[3],
|
||||
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => call.arguments[2],
|
||||
ListKeepIf | ListKeepOks | ListKeepErrs => call.arguments[1],
|
||||
ListSortWith => call.arguments[1],
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
let it = arg_layouts.iter().copied();
|
||||
let bytes = func_name_bytes_help(symbol, it, *ret_layout);
|
||||
let bytes = func_name_bytes_help(*function_name, it, *ret_layout);
|
||||
let name = FuncName(&bytes);
|
||||
let module = MOD_APP;
|
||||
|
||||
{
|
||||
use roc_module::low_level::LowLevel::*;
|
||||
use crate::low_level::HigherOrder::*;
|
||||
|
||||
match op {
|
||||
DictWalk => {
|
||||
let dict = env.symbols[&call.arguments[0]];
|
||||
let state = env.symbols[&call.arguments[1]];
|
||||
let closure_env = env.symbols[&call.arguments[3]];
|
||||
DictWalk { xs, state } => {
|
||||
let dict = env.symbols[xs];
|
||||
let state = env.symbols[state];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
|
||||
let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
|
||||
|
@ -660,10 +647,12 @@ fn call_spec(
|
|||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
||||
ListWalk | ListWalkBackwards | ListWalkUntil => {
|
||||
let list = env.symbols[&call.arguments[0]];
|
||||
let state = env.symbols[&call.arguments[1]];
|
||||
let closure_env = env.symbols[&call.arguments[3]];
|
||||
ListWalk { xs, state }
|
||||
| ListWalkBackwards { xs, state }
|
||||
| ListWalkUntil { xs, state } => {
|
||||
let list = env.symbols[xs];
|
||||
let state = env.symbols[state];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
||||
|
@ -678,9 +667,9 @@ fn call_spec(
|
|||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
||||
ListMapWithIndex => {
|
||||
let list = env.symbols[&call.arguments[0]];
|
||||
let closure_env = env.symbols[&call.arguments[2]];
|
||||
ListMapWithIndex { xs } => {
|
||||
let list = env.symbols[xs];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
||||
|
@ -696,12 +685,12 @@ fn call_spec(
|
|||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
||||
ListMap => {
|
||||
let list1 = env.symbols[&call.arguments[0]];
|
||||
let closure_env = env.symbols[&call.arguments[2]];
|
||||
ListMap { xs } => {
|
||||
let list = env.symbols[xs];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
|
||||
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
|
||||
let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
||||
|
||||
let elem1 = builder.add_bag_get(block, bag1)?;
|
||||
|
||||
|
@ -713,12 +702,12 @@ fn call_spec(
|
|||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
||||
ListSortWith => {
|
||||
let list1 = env.symbols[&call.arguments[0]];
|
||||
let closure_env = env.symbols[&call.arguments[2]];
|
||||
ListSortWith { xs } => {
|
||||
let list = env.symbols[xs];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
|
||||
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
|
||||
let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
||||
|
||||
let elem1 = builder.add_bag_get(block, bag1)?;
|
||||
|
||||
|
@ -730,10 +719,10 @@ fn call_spec(
|
|||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
||||
ListMap2 => {
|
||||
let list1 = env.symbols[&call.arguments[0]];
|
||||
let list2 = env.symbols[&call.arguments[1]];
|
||||
let closure_env = env.symbols[&call.arguments[3]];
|
||||
ListMap2 { xs, ys } => {
|
||||
let list1 = env.symbols[xs];
|
||||
let list2 = env.symbols[ys];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
|
||||
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
|
||||
|
@ -751,11 +740,11 @@ fn call_spec(
|
|||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
||||
ListMap3 => {
|
||||
let list1 = env.symbols[&call.arguments[0]];
|
||||
let list2 = env.symbols[&call.arguments[1]];
|
||||
let list3 = env.symbols[&call.arguments[2]];
|
||||
let closure_env = env.symbols[&call.arguments[4]];
|
||||
ListMap3 { xs, ys, zs } => {
|
||||
let list1 = env.symbols[xs];
|
||||
let list2 = env.symbols[ys];
|
||||
let list3 = env.symbols[zs];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
|
||||
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
|
||||
|
@ -777,9 +766,9 @@ fn call_spec(
|
|||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
||||
ListKeepIf | ListKeepOks | ListKeepErrs => {
|
||||
let list = env.symbols[&call.arguments[0]];
|
||||
let closure_env = env.symbols[&call.arguments[2]];
|
||||
ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => {
|
||||
let list = env.symbols[xs];
|
||||
let closure_env = env.symbols[function_env];
|
||||
|
||||
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
// let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
||||
|
@ -795,17 +784,6 @@ fn call_spec(
|
|||
let unit = builder.add_tuple_type(&[])?;
|
||||
builder.add_unknown_with(block, &[result], unit)?;
|
||||
}
|
||||
|
||||
_ => {
|
||||
// fake a call to the function argument
|
||||
// to make sure the function is specialized
|
||||
|
||||
// very invalid
|
||||
let arg_value_id = build_tuple_value(builder, env, block, &[])?;
|
||||
|
||||
builder.add_call(block, spec_var, module, name, arg_value_id)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO overly pessimstic
|
||||
|
|
|
@ -597,131 +597,84 @@ impl<'a> BorrowInfState<'a> {
|
|||
op,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
function_name,
|
||||
function_env,
|
||||
..
|
||||
} => {
|
||||
use roc_module::low_level::LowLevel::*;
|
||||
|
||||
debug_assert!(op.is_higher_order());
|
||||
use crate::low_level::HigherOrder::*;
|
||||
|
||||
let closure_layout = ProcLayout {
|
||||
arguments: arg_layouts,
|
||||
result: *ret_layout,
|
||||
};
|
||||
|
||||
let function_ps = match param_map.get_symbol(*function_name, closure_layout) {
|
||||
Some(function_ps) => function_ps,
|
||||
None => unreachable!(),
|
||||
};
|
||||
|
||||
match op {
|
||||
ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => {
|
||||
match param_map.get_symbol(arguments[1], closure_layout) {
|
||||
Some(function_ps) => {
|
||||
ListMap { xs }
|
||||
| ListKeepIf { xs }
|
||||
| ListKeepOks { xs }
|
||||
| ListKeepErrs { xs } => {
|
||||
// own the list if the function wants to own the element
|
||||
if !function_ps[0].borrow {
|
||||
self.own_var(arguments[0]);
|
||||
}
|
||||
|
||||
// own the closure environment if the function needs to own it
|
||||
if let Some(false) = function_ps.get(1).map(|p| p.borrow) {
|
||||
self.own_var(arguments[2]);
|
||||
self.own_var(*xs);
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
ListMapWithIndex => {
|
||||
match param_map.get_symbol(arguments[1], closure_layout) {
|
||||
Some(function_ps) => {
|
||||
ListMapWithIndex { xs } => {
|
||||
// own the list if the function wants to own the element
|
||||
if !function_ps[1].borrow {
|
||||
self.own_var(arguments[0]);
|
||||
}
|
||||
|
||||
// own the closure environment if the function needs to own it
|
||||
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
|
||||
self.own_var(arguments[2]);
|
||||
self.own_var(*xs);
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
ListMap2 => match param_map.get_symbol(arguments[2], closure_layout) {
|
||||
Some(function_ps) => {
|
||||
ListMap2 { xs, ys } => {
|
||||
// own the lists if the function wants to own the element
|
||||
if !function_ps[0].borrow {
|
||||
self.own_var(arguments[0]);
|
||||
self.own_var(*xs);
|
||||
}
|
||||
|
||||
if !function_ps[1].borrow {
|
||||
self.own_var(arguments[1]);
|
||||
}
|
||||
|
||||
// own the closure environment if the function needs to own it
|
||||
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
|
||||
self.own_var(arguments[3]);
|
||||
self.own_var(*ys);
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
},
|
||||
ListMap3 => match param_map.get_symbol(arguments[3], closure_layout) {
|
||||
Some(function_ps) => {
|
||||
ListMap3 { xs, ys, zs } => {
|
||||
// own the lists if the function wants to own the element
|
||||
if !function_ps[0].borrow {
|
||||
self.own_var(arguments[0]);
|
||||
self.own_var(*xs);
|
||||
}
|
||||
if !function_ps[1].borrow {
|
||||
self.own_var(arguments[1]);
|
||||
self.own_var(*ys);
|
||||
}
|
||||
if !function_ps[2].borrow {
|
||||
self.own_var(arguments[2]);
|
||||
}
|
||||
|
||||
// own the closure environment if the function needs to own it
|
||||
if let Some(false) = function_ps.get(3).map(|p| p.borrow) {
|
||||
self.own_var(arguments[4]);
|
||||
self.own_var(*zs);
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
},
|
||||
ListSortWith => {
|
||||
match param_map.get_symbol(arguments[1], closure_layout) {
|
||||
Some(function_ps) => {
|
||||
ListSortWith { xs } => {
|
||||
// always own the input list
|
||||
self.own_var(arguments[0]);
|
||||
|
||||
// own the closure environment if the function needs to own it
|
||||
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
|
||||
self.own_var(arguments[2]);
|
||||
self.own_var(*xs);
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => {
|
||||
match param_map.get_symbol(arguments[2], closure_layout) {
|
||||
Some(function_ps) => {
|
||||
ListWalk { xs, state }
|
||||
| ListWalkUntil { xs, state }
|
||||
| ListWalkBackwards { xs, state }
|
||||
| DictWalk { xs, state } => {
|
||||
// own the default value if the function wants to own it
|
||||
if !function_ps[0].borrow {
|
||||
self.own_var(arguments[1]);
|
||||
self.own_var(*state);
|
||||
}
|
||||
|
||||
// own the data structure if the function wants to own the element
|
||||
if !function_ps[1].borrow {
|
||||
self.own_var(arguments[0]);
|
||||
self.own_var(*xs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// own the closure environment if the function needs to own it
|
||||
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
|
||||
self.own_var(arguments[3]);
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// very unsure what demand RunLowLevel should place upon its arguments
|
||||
self.own_var(z);
|
||||
|
||||
let ps = lowlevel_borrow_signature(self.arena, *op);
|
||||
|
||||
self.own_args_using_bools(arguments, ps);
|
||||
}
|
||||
let function_env_position = op.function_arity();
|
||||
if let Some(false) = function_ps.get(function_env_position).map(|p| p.borrow) {
|
||||
self.own_var(*function_env);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -952,7 +905,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
use LowLevel::*;
|
||||
|
||||
// TODO is true or false more efficient for non-refcounted layouts?
|
||||
let irrelevant = OWNED;
|
||||
let irrelevant = BORROWED;
|
||||
let function = irrelevant;
|
||||
let closure_data = irrelevant;
|
||||
let owned = OWNED;
|
||||
|
@ -1000,9 +953,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
|
||||
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked
|
||||
| NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare
|
||||
| NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt
|
||||
| NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy
|
||||
| NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||
| NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow
|
||||
| NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
|
||||
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||
|
||||
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
|
||||
| NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin
|
||||
|
|
|
@ -469,8 +469,13 @@ impl<'a> Context<'a> {
|
|||
specialization_id,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
function_name,
|
||||
function_env,
|
||||
..
|
||||
} => {
|
||||
// setup
|
||||
use crate::low_level::HigherOrder::*;
|
||||
|
||||
macro_rules! create_call {
|
||||
($borrows:expr) => {
|
||||
Expr::Call(crate::ir::Call {
|
||||
|
@ -480,6 +485,8 @@ impl<'a> Context<'a> {
|
|||
closure_env_layout: *closure_env_layout,
|
||||
function_owns_closure_data: true,
|
||||
specialization_id: *specialization_id,
|
||||
function_name: *function_name,
|
||||
function_env: *function_env,
|
||||
arg_layouts,
|
||||
ret_layout: *ret_layout,
|
||||
}
|
||||
|
@ -512,56 +519,28 @@ impl<'a> Context<'a> {
|
|||
result: *ret_layout,
|
||||
};
|
||||
|
||||
let function_ps = match self.param_map.get_symbol(*function_name, function_layout) {
|
||||
Some(function_ps) => function_ps,
|
||||
None => unreachable!(),
|
||||
};
|
||||
|
||||
match op {
|
||||
roc_module::low_level::LowLevel::ListMap
|
||||
| roc_module::low_level::LowLevel::ListKeepIf
|
||||
| roc_module::low_level::LowLevel::ListKeepOks
|
||||
| roc_module::low_level::LowLevel::ListKeepErrs => {
|
||||
match self.param_map.get_symbol(arguments[1], function_layout) {
|
||||
Some(function_ps) => {
|
||||
ListMap { xs }
|
||||
| ListKeepIf { xs }
|
||||
| ListKeepOks { xs }
|
||||
| ListKeepErrs { xs } => {
|
||||
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
|
||||
|
||||
let b = self.add_dec_after_lowlevel(
|
||||
arguments,
|
||||
&borrows,
|
||||
b,
|
||||
b_live_vars,
|
||||
);
|
||||
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
|
||||
|
||||
// if the list is owned, then all elements have been consumed, but not the list itself
|
||||
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b);
|
||||
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
|
||||
|
||||
let v = create_call!(function_ps.get(1));
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
roc_module::low_level::LowLevel::ListMapWithIndex => {
|
||||
match self.param_map.get_symbol(arguments[1], function_layout) {
|
||||
Some(function_ps) => {
|
||||
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];
|
||||
|
||||
let b = self.add_dec_after_lowlevel(
|
||||
arguments,
|
||||
&borrows,
|
||||
b,
|
||||
b_live_vars,
|
||||
);
|
||||
|
||||
let b = decref_if_owned!(function_ps[1].borrow, arguments[0], b);
|
||||
|
||||
let v = create_call!(function_ps.get(2));
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
roc_module::low_level::LowLevel::ListMap2 => {
|
||||
match self.param_map.get_symbol(arguments[2], function_layout) {
|
||||
Some(function_ps) => {
|
||||
ListMap2 { xs, ys } => {
|
||||
let borrows = [
|
||||
function_ps[0].borrow,
|
||||
function_ps[1].borrow,
|
||||
|
@ -569,26 +548,16 @@ impl<'a> Context<'a> {
|
|||
CLOSURE_DATA,
|
||||
];
|
||||
|
||||
let b = self.add_dec_after_lowlevel(
|
||||
arguments,
|
||||
&borrows,
|
||||
b,
|
||||
b_live_vars,
|
||||
);
|
||||
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
|
||||
|
||||
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b);
|
||||
let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b);
|
||||
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
|
||||
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
|
||||
|
||||
let v = create_call!(function_ps.get(2));
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
roc_module::low_level::LowLevel::ListMap3 => {
|
||||
match self.param_map.get_symbol(arguments[3], function_layout) {
|
||||
Some(function_ps) => {
|
||||
ListMap3 { xs, ys, zs } => {
|
||||
let borrows = [
|
||||
function_ps[0].borrow,
|
||||
function_ps[1].borrow,
|
||||
|
@ -597,49 +566,40 @@ impl<'a> Context<'a> {
|
|||
CLOSURE_DATA,
|
||||
];
|
||||
|
||||
let b = self.add_dec_after_lowlevel(
|
||||
arguments,
|
||||
&borrows,
|
||||
b,
|
||||
b_live_vars,
|
||||
);
|
||||
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
|
||||
|
||||
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b);
|
||||
let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b);
|
||||
let b = decref_if_owned!(function_ps[2].borrow, arguments[2], b);
|
||||
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
|
||||
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
|
||||
let b = decref_if_owned!(function_ps[2].borrow, *zs, b);
|
||||
|
||||
let v = create_call!(function_ps.get(3));
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
roc_module::low_level::LowLevel::ListSortWith => {
|
||||
match self.param_map.get_symbol(arguments[1], function_layout) {
|
||||
Some(function_ps) => {
|
||||
let borrows = [OWNED, FUNCTION, CLOSURE_DATA];
|
||||
ListMapWithIndex { xs } => {
|
||||
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];
|
||||
|
||||
let b = self.add_dec_after_lowlevel(
|
||||
arguments,
|
||||
&borrows,
|
||||
b,
|
||||
b_live_vars,
|
||||
);
|
||||
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
|
||||
|
||||
let b = decref_if_owned!(function_ps[1].borrow, *xs, b);
|
||||
|
||||
let v = create_call!(function_ps.get(2));
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
None => unreachable!(),
|
||||
ListSortWith { xs: _ } => {
|
||||
let borrows = [OWNED, FUNCTION, CLOSURE_DATA];
|
||||
|
||||
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
|
||||
|
||||
let v = create_call!(function_ps.get(2));
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
}
|
||||
roc_module::low_level::LowLevel::ListWalk
|
||||
| roc_module::low_level::LowLevel::ListWalkUntil
|
||||
| roc_module::low_level::LowLevel::ListWalkBackwards
|
||||
| roc_module::low_level::LowLevel::DictWalk => {
|
||||
match self.param_map.get_symbol(arguments[2], function_layout) {
|
||||
Some(function_ps) => {
|
||||
ListWalk { xs, state: _ }
|
||||
| ListWalkUntil { xs, state: _ }
|
||||
| ListWalkBackwards { xs, state: _ }
|
||||
| DictWalk { xs, state: _ } => {
|
||||
// borrow data structure based on first argument of the folded function
|
||||
// borrow the default based on second argument of the folded function
|
||||
let borrows = [
|
||||
|
@ -649,31 +609,12 @@ impl<'a> Context<'a> {
|
|||
CLOSURE_DATA,
|
||||
];
|
||||
|
||||
let b = self.add_dec_after_lowlevel(
|
||||
arguments,
|
||||
&borrows,
|
||||
b,
|
||||
b_live_vars,
|
||||
);
|
||||
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
|
||||
|
||||
let b = decref_if_owned!(function_ps[1].borrow, arguments[0], b);
|
||||
let b = decref_if_owned!(function_ps[1].borrow, *xs, b);
|
||||
|
||||
let v = create_call!(function_ps.get(2));
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
|
||||
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
|
||||
|
||||
let v = Expr::Call(crate::ir::Call {
|
||||
call_type,
|
||||
arguments,
|
||||
});
|
||||
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1102,14 +1102,23 @@ pub enum CallType<'a> {
|
|||
update_mode: UpdateModeId,
|
||||
},
|
||||
HigherOrderLowLevel {
|
||||
op: LowLevel,
|
||||
op: crate::low_level::HigherOrder,
|
||||
/// the layout of the closure argument, if any
|
||||
closure_env_layout: Option<Layout<'a>>,
|
||||
/// specialization id of the function argument
|
||||
specialization_id: CallSpecId,
|
||||
/// does the function need to own the closure data
|
||||
|
||||
/// name of the top-level function that is passed as an argument
|
||||
/// e.g. in `List.map xs Num.abs` this would be `Num.abs`
|
||||
function_name: Symbol,
|
||||
|
||||
/// Symbol of the environment captured by the function argument
|
||||
function_env: Symbol,
|
||||
|
||||
/// does the function argument need to own the closure data
|
||||
function_owns_closure_data: bool,
|
||||
/// function layout
|
||||
|
||||
/// specialization id of the function argument, used for name generation
|
||||
specialization_id: CallSpecId,
|
||||
/// function layout, used for name generation
|
||||
arg_layouts: &'a [Layout<'a>],
|
||||
ret_layout: Layout<'a>,
|
||||
},
|
||||
|
@ -2679,48 +2688,6 @@ fn specialize_naked_symbol<'a>(
|
|||
)
|
||||
}
|
||||
|
||||
macro_rules! match_on_closure_argument {
|
||||
($env:expr, $procs:expr, $layout_cache:expr, $closure_data_symbol:expr, $closure_data_var:expr, $op:expr, [$($x:expr),* $(,)?], $layout: expr, $assigned:expr, $hole:expr) => {{
|
||||
let closure_data_layout = return_on_layout_error!(
|
||||
$env,
|
||||
$layout_cache.raw_from_var($env.arena, $closure_data_var, $env.subs)
|
||||
);
|
||||
|
||||
let top_level = ProcLayout::from_raw($env.arena, closure_data_layout);
|
||||
|
||||
let arena = $env.arena;
|
||||
|
||||
let arg_layouts = top_level.arguments;
|
||||
let ret_layout = top_level.result;
|
||||
|
||||
match closure_data_layout {
|
||||
RawFunctionLayout::Function(_, lambda_set, _) => {
|
||||
lowlevel_match_on_lambda_set(
|
||||
$env,
|
||||
lambda_set,
|
||||
$op,
|
||||
$closure_data_symbol,
|
||||
|top_level_function, closure_data, closure_env_layout, specialization_id| self::Call {
|
||||
call_type: CallType::HigherOrderLowLevel {
|
||||
op: $op,
|
||||
closure_env_layout,
|
||||
specialization_id,
|
||||
function_owns_closure_data: false,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
},
|
||||
arguments: arena.alloc([$($x,)* top_level_function, closure_data]),
|
||||
},
|
||||
$layout,
|
||||
$assigned,
|
||||
$hole,
|
||||
)
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
fn try_make_literal<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
can_expr: &roc_can::expr::Expr,
|
||||
|
@ -4013,51 +3980,66 @@ pub fn with_hole<'a>(
|
|||
let layout =
|
||||
return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs));
|
||||
|
||||
use LowLevel::*;
|
||||
match op {
|
||||
ListMap | ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs
|
||||
| ListSortWith => {
|
||||
debug_assert_eq!(arg_symbols.len(), 2);
|
||||
|
||||
let closure_index = 1;
|
||||
macro_rules! match_on_closure_argument {
|
||||
( $ho:ident, [$($x:ident),* $(,)?]) => {{
|
||||
let closure_index = op.function_argument_position();
|
||||
let closure_data_symbol = arg_symbols[closure_index];
|
||||
let closure_data_var = args[closure_index].0;
|
||||
|
||||
match_on_closure_argument!(
|
||||
let closure_data_layout = return_on_layout_error!(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
closure_data_symbol,
|
||||
closure_data_var,
|
||||
layout_cache.raw_from_var(env.arena, closure_data_var, env.subs)
|
||||
);
|
||||
|
||||
let top_level = ProcLayout::from_raw(env.arena, closure_data_layout);
|
||||
|
||||
let arena = env.arena;
|
||||
|
||||
let arg_layouts = top_level.arguments;
|
||||
let ret_layout = top_level.result;
|
||||
|
||||
match closure_data_layout {
|
||||
RawFunctionLayout::Function(_, lambda_set, _) => {
|
||||
lowlevel_match_on_lambda_set(
|
||||
env,
|
||||
lambda_set,
|
||||
op,
|
||||
[arg_symbols[0]],
|
||||
closure_data_symbol,
|
||||
|top_level_function, closure_data, closure_env_layout, specialization_id| self::Call {
|
||||
call_type: CallType::HigherOrderLowLevel {
|
||||
op: crate::low_level::HigherOrder::$ho { $($x,)* },
|
||||
closure_env_layout,
|
||||
specialization_id,
|
||||
function_owns_closure_data: false,
|
||||
function_env: closure_data_symbol,
|
||||
function_name: top_level_function,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
},
|
||||
arguments: arena.alloc([$($x,)* top_level_function, closure_data]),
|
||||
},
|
||||
layout,
|
||||
assigned,
|
||||
hole
|
||||
hole,
|
||||
)
|
||||
}
|
||||
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => {
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! walk {
|
||||
($oh:ident) => {{
|
||||
debug_assert_eq!(arg_symbols.len(), 3);
|
||||
|
||||
const LIST_INDEX: usize = 0;
|
||||
const DEFAULT_INDEX: usize = 1;
|
||||
const CLOSURE_INDEX: usize = 2;
|
||||
|
||||
let closure_data_symbol = arg_symbols[CLOSURE_INDEX];
|
||||
let closure_data_var = args[CLOSURE_INDEX].0;
|
||||
let xs = arg_symbols[LIST_INDEX];
|
||||
let state = arg_symbols[DEFAULT_INDEX];
|
||||
|
||||
let stmt = match_on_closure_argument!(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
closure_data_symbol,
|
||||
closure_data_var,
|
||||
op,
|
||||
[arg_symbols[LIST_INDEX], arg_symbols[DEFAULT_INDEX]],
|
||||
layout,
|
||||
assigned,
|
||||
hole
|
||||
);
|
||||
let stmt = match_on_closure_argument!($oh, [xs, state]);
|
||||
|
||||
// because of a hack to implement List.product and List.sum, we need to also
|
||||
// assign to symbols here. Normally the arguments to a lowlevel function are
|
||||
|
@ -4094,46 +4076,62 @@ pub fn with_hole<'a>(
|
|||
arg_symbols[CLOSURE_INDEX],
|
||||
stmt,
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
use LowLevel::*;
|
||||
match op {
|
||||
ListMap => {
|
||||
debug_assert_eq!(arg_symbols.len(), 2);
|
||||
let xs = arg_symbols[0];
|
||||
match_on_closure_argument!(ListMap, [xs])
|
||||
}
|
||||
|
||||
ListMapWithIndex => {
|
||||
debug_assert_eq!(arg_symbols.len(), 2);
|
||||
let xs = arg_symbols[0];
|
||||
match_on_closure_argument!(ListMapWithIndex, [xs])
|
||||
}
|
||||
ListKeepIf => {
|
||||
debug_assert_eq!(arg_symbols.len(), 2);
|
||||
let xs = arg_symbols[0];
|
||||
match_on_closure_argument!(ListKeepIf, [xs])
|
||||
}
|
||||
ListKeepOks => {
|
||||
debug_assert_eq!(arg_symbols.len(), 2);
|
||||
let xs = arg_symbols[0];
|
||||
match_on_closure_argument!(ListKeepOks, [xs])
|
||||
}
|
||||
ListKeepErrs => {
|
||||
debug_assert_eq!(arg_symbols.len(), 2);
|
||||
let xs = arg_symbols[0];
|
||||
match_on_closure_argument!(ListKeepErrs, [xs])
|
||||
}
|
||||
ListSortWith => {
|
||||
debug_assert_eq!(arg_symbols.len(), 2);
|
||||
let xs = arg_symbols[0];
|
||||
match_on_closure_argument!(ListSortWith, [xs])
|
||||
}
|
||||
ListWalk => walk!(ListWalk),
|
||||
ListWalkUntil => walk!(ListWalkUntil),
|
||||
ListWalkBackwards => walk!(ListWalkBackwards),
|
||||
DictWalk => walk!(DictWalk),
|
||||
ListMap2 => {
|
||||
debug_assert_eq!(arg_symbols.len(), 3);
|
||||
|
||||
let closure_index = 2;
|
||||
let closure_data_symbol = arg_symbols[closure_index];
|
||||
let closure_data_var = args[closure_index].0;
|
||||
let xs = arg_symbols[0];
|
||||
let ys = arg_symbols[1];
|
||||
|
||||
match_on_closure_argument!(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
closure_data_symbol,
|
||||
closure_data_var,
|
||||
op,
|
||||
[arg_symbols[0], arg_symbols[1]],
|
||||
layout,
|
||||
assigned,
|
||||
hole
|
||||
)
|
||||
match_on_closure_argument!(ListMap2, [xs, ys])
|
||||
}
|
||||
ListMap3 => {
|
||||
debug_assert_eq!(arg_symbols.len(), 4);
|
||||
|
||||
let closure_index = 3;
|
||||
let closure_data_symbol = arg_symbols[closure_index];
|
||||
let closure_data_var = args[closure_index].0;
|
||||
let xs = arg_symbols[0];
|
||||
let ys = arg_symbols[1];
|
||||
let zs = arg_symbols[2];
|
||||
|
||||
match_on_closure_argument!(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
closure_data_symbol,
|
||||
closure_data_var,
|
||||
op,
|
||||
[arg_symbols[0], arg_symbols[1], arg_symbols[2]],
|
||||
layout,
|
||||
assigned,
|
||||
hole
|
||||
)
|
||||
match_on_closure_argument!(ListMap3, [xs, ys, zs])
|
||||
}
|
||||
_ => {
|
||||
let call = self::Call {
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod expand_rc;
|
|||
pub mod inc_dec;
|
||||
pub mod ir;
|
||||
pub mod layout;
|
||||
pub mod low_level;
|
||||
pub mod reset_reuse;
|
||||
pub mod tail_recursion;
|
||||
|
||||
|
|
329
compiler/mono/src/low_level.rs
Normal file
329
compiler/mono/src/low_level.rs
Normal file
|
@ -0,0 +1,329 @@
|
|||
use roc_module::symbol::Symbol;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum HigherOrder {
|
||||
ListMap { xs: Symbol },
|
||||
ListMap2 { xs: Symbol, ys: Symbol },
|
||||
ListMap3 { xs: Symbol, ys: Symbol, zs: Symbol },
|
||||
ListMapWithIndex { xs: Symbol },
|
||||
ListKeepIf { xs: Symbol },
|
||||
ListWalk { xs: Symbol, state: Symbol },
|
||||
ListWalkUntil { xs: Symbol, state: Symbol },
|
||||
ListWalkBackwards { xs: Symbol, state: Symbol },
|
||||
ListKeepOks { xs: Symbol },
|
||||
ListKeepErrs { xs: Symbol },
|
||||
ListSortWith { xs: Symbol },
|
||||
DictWalk { xs: Symbol, state: Symbol },
|
||||
}
|
||||
|
||||
impl HigherOrder {
|
||||
pub fn function_arity(&self) -> usize {
|
||||
match self {
|
||||
HigherOrder::ListMap { .. } => 1,
|
||||
HigherOrder::ListMap2 { .. } => 2,
|
||||
HigherOrder::ListMap3 { .. } => 3,
|
||||
HigherOrder::ListMapWithIndex { .. } => 2,
|
||||
HigherOrder::ListKeepIf { .. } => 1,
|
||||
HigherOrder::ListWalk { .. } => 2,
|
||||
HigherOrder::ListWalkUntil { .. } => 2,
|
||||
HigherOrder::ListWalkBackwards { .. } => 2,
|
||||
HigherOrder::ListKeepOks { .. } => 1,
|
||||
HigherOrder::ListKeepErrs { .. } => 1,
|
||||
HigherOrder::ListSortWith { .. } => 2,
|
||||
HigherOrder::DictWalk { .. } => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum FirstOrder {
|
||||
StrConcat,
|
||||
StrJoinWith,
|
||||
StrIsEmpty,
|
||||
StrStartsWith,
|
||||
StrStartsWithCodePt,
|
||||
StrEndsWith,
|
||||
StrSplit,
|
||||
StrCountGraphemes,
|
||||
StrFromInt,
|
||||
StrFromUtf8,
|
||||
StrFromUtf8Range,
|
||||
StrToUtf8,
|
||||
StrRepeat,
|
||||
StrFromFloat,
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
ListSet,
|
||||
ListDrop,
|
||||
ListDropAt,
|
||||
ListSingle,
|
||||
ListRepeat,
|
||||
ListReverse,
|
||||
ListConcat,
|
||||
ListContains,
|
||||
ListAppend,
|
||||
ListPrepend,
|
||||
ListJoin,
|
||||
ListRange,
|
||||
ListSwap,
|
||||
DictSize,
|
||||
DictEmpty,
|
||||
DictInsert,
|
||||
DictRemove,
|
||||
DictContains,
|
||||
DictGetUnsafe,
|
||||
DictKeys,
|
||||
DictValues,
|
||||
DictUnion,
|
||||
DictIntersection,
|
||||
DictDifference,
|
||||
SetFromList,
|
||||
NumAdd,
|
||||
NumAddWrap,
|
||||
NumAddChecked,
|
||||
NumSub,
|
||||
NumSubWrap,
|
||||
NumSubChecked,
|
||||
NumMul,
|
||||
NumMulWrap,
|
||||
NumMulChecked,
|
||||
NumGt,
|
||||
NumGte,
|
||||
NumLt,
|
||||
NumLte,
|
||||
NumCompare,
|
||||
NumDivUnchecked,
|
||||
NumRemUnchecked,
|
||||
NumIsMultipleOf,
|
||||
NumAbs,
|
||||
NumNeg,
|
||||
NumSin,
|
||||
NumCos,
|
||||
NumSqrtUnchecked,
|
||||
NumLogUnchecked,
|
||||
NumRound,
|
||||
NumToFloat,
|
||||
NumPow,
|
||||
NumCeiling,
|
||||
NumPowInt,
|
||||
NumFloor,
|
||||
NumIsFinite,
|
||||
NumAtan,
|
||||
NumAcos,
|
||||
NumAsin,
|
||||
NumBitwiseAnd,
|
||||
NumBitwiseXor,
|
||||
NumBitwiseOr,
|
||||
NumShiftLeftBy,
|
||||
NumShiftRightBy,
|
||||
NumBytesToU16,
|
||||
NumBytesToU32,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
Eq,
|
||||
NotEq,
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
Hash,
|
||||
ExpectTrue,
|
||||
}
|
||||
|
||||
/*
|
||||
enum FirstOrHigher {
|
||||
First(FirstOrder),
|
||||
Higher(HigherOrder),
|
||||
}
|
||||
|
||||
fn from_low_level(low_level: &LowLevel, arguments: &[Symbol]) -> FirstOrHigher {
|
||||
use FirstOrHigher::*;
|
||||
use FirstOrder::*;
|
||||
use HigherOrder::*;
|
||||
|
||||
match low_level {
|
||||
LowLevel::StrConcat => First(StrConcat),
|
||||
LowLevel::StrJoinWith => First(StrJoinWith),
|
||||
LowLevel::StrIsEmpty => First(StrIsEmpty),
|
||||
LowLevel::StrStartsWith => First(StrStartsWith),
|
||||
LowLevel::StrStartsWithCodePt => First(StrStartsWithCodePt),
|
||||
LowLevel::StrEndsWith => First(StrEndsWith),
|
||||
LowLevel::StrSplit => First(StrSplit),
|
||||
LowLevel::StrCountGraphemes => First(StrCountGraphemes),
|
||||
LowLevel::StrFromInt => First(StrFromInt),
|
||||
LowLevel::StrFromUtf8 => First(StrFromUtf8),
|
||||
LowLevel::StrFromUtf8Range => First(StrFromUtf8Range),
|
||||
LowLevel::StrToUtf8 => First(StrToUtf8),
|
||||
LowLevel::StrRepeat => First(StrRepeat),
|
||||
LowLevel::StrFromFloat => First(StrFromFloat),
|
||||
LowLevel::ListLen => First(ListLen),
|
||||
LowLevel::ListGetUnsafe => First(ListGetUnsafe),
|
||||
LowLevel::ListSet => First(ListSet),
|
||||
LowLevel::ListDrop => First(ListDrop),
|
||||
LowLevel::ListDropAt => First(ListDropAt),
|
||||
LowLevel::ListSingle => First(ListSingle),
|
||||
LowLevel::ListRepeat => First(ListRepeat),
|
||||
LowLevel::ListReverse => First(ListReverse),
|
||||
LowLevel::ListConcat => First(ListConcat),
|
||||
LowLevel::ListContains => First(ListContains),
|
||||
LowLevel::ListAppend => First(ListAppend),
|
||||
LowLevel::ListPrepend => First(ListPrepend),
|
||||
LowLevel::ListJoin => First(ListJoin),
|
||||
LowLevel::ListRange => First(ListRange),
|
||||
LowLevel::ListSwap => First(ListSwap),
|
||||
LowLevel::DictSize => First(DictSize),
|
||||
LowLevel::DictEmpty => First(DictEmpty),
|
||||
LowLevel::DictInsert => First(DictInsert),
|
||||
LowLevel::DictRemove => First(DictRemove),
|
||||
LowLevel::DictContains => First(DictContains),
|
||||
LowLevel::DictGetUnsafe => First(DictGetUnsafe),
|
||||
LowLevel::DictKeys => First(DictKeys),
|
||||
LowLevel::DictValues => First(DictValues),
|
||||
LowLevel::DictUnion => First(DictUnion),
|
||||
LowLevel::DictIntersection => First(DictIntersection),
|
||||
LowLevel::DictDifference => First(DictDifference),
|
||||
LowLevel::SetFromList => First(SetFromList),
|
||||
LowLevel::NumAdd => First(NumAdd),
|
||||
LowLevel::NumAddWrap => First(NumAddWrap),
|
||||
LowLevel::NumAddChecked => First(NumAddChecked),
|
||||
LowLevel::NumSub => First(NumSub),
|
||||
LowLevel::NumSubWrap => First(NumSubWrap),
|
||||
LowLevel::NumSubChecked => First(NumSubChecked),
|
||||
LowLevel::NumMul => First(NumMul),
|
||||
LowLevel::NumMulWrap => First(NumMulWrap),
|
||||
LowLevel::NumMulChecked => First(NumMulChecked),
|
||||
LowLevel::NumGt => First(NumGt),
|
||||
LowLevel::NumGte => First(NumGte),
|
||||
LowLevel::NumLt => First(NumLt),
|
||||
LowLevel::NumLte => First(NumLte),
|
||||
LowLevel::NumCompare => First(NumCompare),
|
||||
LowLevel::NumDivUnchecked => First(NumDivUnchecked),
|
||||
LowLevel::NumRemUnchecked => First(NumRemUnchecked),
|
||||
LowLevel::NumIsMultipleOf => First(NumIsMultipleOf),
|
||||
LowLevel::NumAbs => First(NumAbs),
|
||||
LowLevel::NumNeg => First(NumNeg),
|
||||
LowLevel::NumSin => First(NumSin),
|
||||
LowLevel::NumCos => First(NumCos),
|
||||
LowLevel::NumSqrtUnchecked => First(NumSqrtUnchecked),
|
||||
LowLevel::NumLogUnchecked => First(NumLogUnchecked),
|
||||
LowLevel::NumRound => First(NumRound),
|
||||
LowLevel::NumToFloat => First(NumToFloat),
|
||||
LowLevel::NumPow => First(NumPow),
|
||||
LowLevel::NumCeiling => First(NumCeiling),
|
||||
LowLevel::NumPowInt => First(NumPowInt),
|
||||
LowLevel::NumFloor => First(NumFloor),
|
||||
LowLevel::NumIsFinite => First(NumIsFinite),
|
||||
LowLevel::NumAtan => First(NumAtan),
|
||||
LowLevel::NumAcos => First(NumAcos),
|
||||
LowLevel::NumAsin => First(NumAsin),
|
||||
LowLevel::NumBitwiseAnd => First(NumBitwiseAnd),
|
||||
LowLevel::NumBitwiseXor => First(NumBitwiseXor),
|
||||
LowLevel::NumBitwiseOr => First(NumBitwiseOr),
|
||||
LowLevel::NumShiftLeftBy => First(NumShiftLeftBy),
|
||||
LowLevel::NumShiftRightBy => First(NumShiftRightBy),
|
||||
LowLevel::NumBytesToU16 => First(NumBytesToU16),
|
||||
LowLevel::NumBytesToU32 => First(NumBytesToU32),
|
||||
LowLevel::NumShiftRightZfBy => First(NumShiftRightZfBy),
|
||||
LowLevel::NumIntCast => First(NumIntCast),
|
||||
LowLevel::Eq => First(Eq),
|
||||
LowLevel::NotEq => First(NotEq),
|
||||
LowLevel::And => First(And),
|
||||
LowLevel::Or => First(Or),
|
||||
LowLevel::Not => First(Not),
|
||||
LowLevel::Hash => First(Hash),
|
||||
LowLevel::ExpectTrue => First(ExpectTrue),
|
||||
LowLevel::ListMap => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListMap {
|
||||
xs: arguments[0],
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListMap2 => {
|
||||
debug_assert_eq!(arguments.len(), 4);
|
||||
Higher(ListMap2 {
|
||||
xs: arguments[0],
|
||||
ys: arguments[1],
|
||||
function_name: arguments[2],
|
||||
function_env: arguments[3],
|
||||
})
|
||||
}
|
||||
LowLevel::ListMap3 => {
|
||||
debug_assert_eq!(arguments.len(), 5);
|
||||
Higher(ListMap3 {
|
||||
xs: arguments[0],
|
||||
ys: arguments[1],
|
||||
zs: arguments[2],
|
||||
function_name: arguments[3],
|
||||
function_env: arguments[4],
|
||||
})
|
||||
}
|
||||
LowLevel::ListMapWithIndex => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListMapWithIndex {
|
||||
xs: arguments[0],
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListKeepIf => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListKeepIf {
|
||||
xs: arguments[0],
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListWalk => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListWalk {
|
||||
xs: arguments[0],
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListWalkUntil => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListWalkUntil {
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListWalkBackwards => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListWalkBackwards {
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListKeepOks => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListKeepOks {
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListKeepErrs => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListKeepErrs {
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::ListSortWith => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(ListSortWith {
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
LowLevel::DictWalk => {
|
||||
debug_assert_eq!(arguments.len(), 3);
|
||||
Higher(DictWalk {
|
||||
function_name: arguments[1],
|
||||
function_env: arguments[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -3305,6 +3305,18 @@ mod solve_expr {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_ceil() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.divCeil
|
||||
"#
|
||||
),
|
||||
"Int a, Int a -> Result (Int a) [ DivByZero ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_int() {
|
||||
infer_eq_without_problem(
|
||||
|
|
|
@ -77,11 +77,11 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
|
||||
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
|
||||
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
||||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue