mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 08:17:40 +03:00
Merge pull request #207 from rtfeldman/list-int
Basic List Int code gen in Cranelift
This commit is contained in:
commit
007e8340a0
@ -372,6 +372,14 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
),
|
||||
);
|
||||
|
||||
add_type(
|
||||
Symbol::LIST_GET_UNSAFE, // TODO remove this once we can code gen Result
|
||||
SolvedType::Func(
|
||||
vec![list_type(flex(TVAR1)), int_type()],
|
||||
Box::new(flex(TVAR1)),
|
||||
),
|
||||
);
|
||||
|
||||
// set : List a, Int, a -> List a
|
||||
add_type(
|
||||
Symbol::LIST_SET,
|
||||
|
@ -37,7 +37,7 @@ pub enum Expr {
|
||||
Str(Box<str>),
|
||||
BlockStr(Box<str>),
|
||||
List {
|
||||
entry_var: Variable,
|
||||
elem_var: Variable,
|
||||
loc_elems: Vec<Located<Expr>>,
|
||||
},
|
||||
|
||||
@ -201,7 +201,7 @@ pub fn canonicalize_expr<'a>(
|
||||
if loc_elems.is_empty() {
|
||||
(
|
||||
List {
|
||||
entry_var: var_store.fresh(),
|
||||
elem_var: var_store.fresh(),
|
||||
loc_elems: Vec::new(),
|
||||
},
|
||||
Output::default(),
|
||||
@ -228,7 +228,7 @@ pub fn canonicalize_expr<'a>(
|
||||
|
||||
(
|
||||
List {
|
||||
entry_var: var_store.fresh(),
|
||||
elem_var: var_store.fresh(),
|
||||
loc_elems: can_elems,
|
||||
},
|
||||
output,
|
||||
|
@ -171,17 +171,17 @@ pub fn constrain_expr(
|
||||
}
|
||||
Str(_) | BlockStr(_) => Eq(str_type(), expected, region),
|
||||
List {
|
||||
entry_var,
|
||||
elem_var,
|
||||
loc_elems,
|
||||
..
|
||||
} => {
|
||||
if loc_elems.is_empty() {
|
||||
exists(
|
||||
vec![*entry_var],
|
||||
Eq(empty_list_type(*entry_var), expected, region),
|
||||
vec![*elem_var],
|
||||
Eq(empty_list_type(*elem_var), expected, region),
|
||||
)
|
||||
} else {
|
||||
let list_elem_type = Type::Variable(*entry_var);
|
||||
let list_elem_type = Type::Variable(*elem_var);
|
||||
let mut constraints = Vec::with_capacity(1 + loc_elems.len());
|
||||
|
||||
for loc_elem in loc_elems {
|
||||
@ -195,7 +195,7 @@ pub fn constrain_expr(
|
||||
|
||||
constraints.push(Eq(list_type(list_elem_type), expected, region));
|
||||
|
||||
exists(vec![*entry_var], And(constraints))
|
||||
exists(vec![*elem_var], And(constraints))
|
||||
}
|
||||
}
|
||||
Call(boxed, loc_args, _application_style) => {
|
||||
|
@ -247,8 +247,37 @@ pub fn build_expr<'a, B: Backend>(
|
||||
// TODO: Instead of NUL-terminating, return a struct
|
||||
// with the pointer and also the length and capacity.
|
||||
let nul_terminator = builder.ins().iconst(types::I8, 0);
|
||||
let index = bytes_len as u64 - 1;
|
||||
let offset = Offset32::new(index as i32);
|
||||
let index = bytes_len as i32 - 1;
|
||||
let offset = Offset32::new(index);
|
||||
|
||||
builder.ins().store(mem_flags, nul_terminator, ptr, offset);
|
||||
|
||||
ptr
|
||||
}
|
||||
}
|
||||
Array { elem_layout, elems } => {
|
||||
if elems.is_empty() {
|
||||
panic!("TODO build an empty Array in Crane");
|
||||
} else {
|
||||
let elem_bytes = elem_layout.stack_size(env.cfg) as usize;
|
||||
let bytes_len = (elem_bytes * elems.len()) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */;
|
||||
let ptr = call_malloc(env, module, builder, bytes_len);
|
||||
let mem_flags = MemFlags::new();
|
||||
|
||||
// Copy the elements from the literal into the array
|
||||
for (index, elem) in elems.iter().enumerate() {
|
||||
let offset = Offset32::new(elem_bytes as i32 * index as i32);
|
||||
let val = build_expr(env, scope, module, builder, elem, procs);
|
||||
|
||||
builder.ins().store(mem_flags, val, ptr, offset);
|
||||
}
|
||||
|
||||
// Add a NUL terminator at the end.
|
||||
// TODO: Instead of NUL-terminating, return a struct
|
||||
// with the pointer and also the length and capacity.
|
||||
let nul_terminator = builder.ins().iconst(types::I8, 0);
|
||||
let index = bytes_len as i32 - 1;
|
||||
let offset = Offset32::new(index);
|
||||
|
||||
builder.ins().store(mem_flags, nul_terminator, ptr, offset);
|
||||
|
||||
@ -555,6 +584,26 @@ fn call_with_args<'a, B: Backend>(
|
||||
debug_assert!(args.len() == 2);
|
||||
builder.ins().imul(args[0], args[1])
|
||||
}
|
||||
Symbol::LIST_GET_UNSAFE => {
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
let list_ptr = args[0];
|
||||
let elem_index = args[1];
|
||||
|
||||
let elem_type = Type::int(64).unwrap(); // TODO Look this up instead of hardcoding it!
|
||||
let elem_bytes = 8; // TODO Look this up instead of hardcoding it!
|
||||
let elem_size = builder.ins().iconst(types::I64, elem_bytes);
|
||||
|
||||
// Multiply the requested index by the size of each element.
|
||||
let offset = builder.ins().imul(elem_index, elem_size);
|
||||
|
||||
builder.ins().load_complex(
|
||||
elem_type,
|
||||
MemFlags::new(),
|
||||
&[list_ptr, offset],
|
||||
Offset32::new(0),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let fn_id = match scope.get(&symbol) {
|
||||
Some(ScopeEntry::Func{ func_id, .. }) => *func_id,
|
||||
|
@ -606,6 +606,7 @@ define_builtins! {
|
||||
3 LIST_GET: "get"
|
||||
4 LIST_SET: "set"
|
||||
5 LIST_MAP: "map"
|
||||
6 LIST_GET_UNSAFE: "getUnsafe" // TODO remove once we can code gen Result
|
||||
}
|
||||
7 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
@ -105,6 +105,11 @@ pub enum Expr<'a> {
|
||||
struct_layout: Layout<'a>,
|
||||
},
|
||||
|
||||
Array {
|
||||
elem_layout: Layout<'a>,
|
||||
elems: &'a [Expr<'a>],
|
||||
},
|
||||
|
||||
RuntimeError(&'a str),
|
||||
}
|
||||
|
||||
@ -287,6 +292,31 @@ fn from_can<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
List {
|
||||
elem_var,
|
||||
loc_elems,
|
||||
} => {
|
||||
let subs = env.subs;
|
||||
let arena = env.arena;
|
||||
let content = subs.get_without_compacting(elem_var).content;
|
||||
let elem_layout = match Layout::from_content(arena, content, subs) {
|
||||
Ok(layout) => layout,
|
||||
Err(()) => {
|
||||
panic!("TODO gracefully handle List with invalid element layout");
|
||||
}
|
||||
};
|
||||
|
||||
let mut elems = Vec::with_capacity_in(loc_elems.len(), arena);
|
||||
|
||||
for loc_elem in loc_elems {
|
||||
elems.push(from_can(env, loc_elem.value, procs, None));
|
||||
}
|
||||
|
||||
Expr::Array {
|
||||
elem_layout,
|
||||
elems: elems.into_bump_slice(),
|
||||
}
|
||||
}
|
||||
other => panic!("TODO convert canonicalized {:?} to ll::Expr", other),
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,10 @@ impl<'a> Layout<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO this will probably need to move to crane:: because
|
||||
/// LLVM gets the answer using different APIs! Also, might be
|
||||
/// nice to rename it to bytes_size, both to include the units
|
||||
/// and also because this is the size on the stack *and* the heap!
|
||||
pub fn stack_size(&self, cfg: TargetFrontendConfig) -> u32 {
|
||||
use Layout::*;
|
||||
|
||||
@ -252,7 +256,7 @@ fn layout_from_flat_type<'a>(
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("TODO handle a tag union with mutliple tags.");
|
||||
panic!("TODO handle a tag union with mutliple tags: {:?}", tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -493,16 +493,16 @@ pub fn constrain_expr(
|
||||
exists(vars, And(arg_cons))
|
||||
}
|
||||
List {
|
||||
entry_var,
|
||||
elem_var,
|
||||
loc_elems,
|
||||
} => {
|
||||
let uniq_var = var_store.fresh();
|
||||
if loc_elems.is_empty() {
|
||||
let inferred = builtins::empty_list_type(Bool::variable(uniq_var), *entry_var);
|
||||
exists(vec![*entry_var, uniq_var], Eq(inferred, expected, region))
|
||||
let inferred = builtins::empty_list_type(Bool::variable(uniq_var), *elem_var);
|
||||
exists(vec![*elem_var, uniq_var], Eq(inferred, expected, region))
|
||||
} else {
|
||||
// constrain `expected ~ List a` and that all elements `~ a`.
|
||||
let entry_type = Type::Variable(*entry_var);
|
||||
let entry_type = Type::Variable(*elem_var);
|
||||
let mut constraints = Vec::with_capacity(1 + loc_elems.len());
|
||||
|
||||
for loc_elem in loc_elems.iter() {
|
||||
@ -524,7 +524,7 @@ pub fn constrain_expr(
|
||||
let inferred = list_type(Bool::variable(uniq_var), entry_type);
|
||||
constraints.push(Eq(inferred, expected, region));
|
||||
|
||||
exists(vec![*entry_var, uniq_var], And(constraints))
|
||||
exists(vec![*elem_var, uniq_var], And(constraints))
|
||||
}
|
||||
}
|
||||
Var(symbol) => {
|
||||
|
@ -343,15 +343,20 @@ mod test_gen {
|
||||
assert_evals_to!("1234.0", 1234.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_int_list() {
|
||||
assert_crane_evals_to!("List.getUnsafe [ 12, 9, 6, 3 ] 1", 9, i64, |a| a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_first_float() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 1.23 is
|
||||
1.23 -> 12
|
||||
_ -> 34
|
||||
"#
|
||||
when 1.23 is
|
||||
1.23 -> 12
|
||||
_ -> 34
|
||||
"#
|
||||
),
|
||||
12,
|
||||
i64
|
||||
|
@ -2228,10 +2228,10 @@ mod test_infer {
|
||||
list
|
||||
|> List.set i atJ
|
||||
|> List.set j atI
|
||||
|
||||
|
||||
_ ->
|
||||
list
|
||||
|
||||
|
||||
partition : Int, Int, List Int -> [ Pair Int (List Int) ]
|
||||
partition = \low, high, initialList ->
|
||||
when List.get initialList high is
|
||||
@ -2244,19 +2244,19 @@ mod test_infer {
|
||||
go (i + 1) (j + 1) (swap (i + 1) j list)
|
||||
else
|
||||
go i (j + 1) list
|
||||
|
||||
|
||||
Err _ ->
|
||||
Pair i list
|
||||
else
|
||||
Pair i list
|
||||
|
||||
|
||||
when go (low - 1) low initialList is
|
||||
Pair newI newList ->
|
||||
Pair (newI + 1) (swap (newI + 1) high newList)
|
||||
|
||||
|
||||
Err _ ->
|
||||
Pair (low - 1) initialList
|
||||
|
||||
|
||||
partition
|
||||
"#
|
||||
),
|
||||
@ -2282,4 +2282,16 @@ mod test_infer {
|
||||
"List Int -> List Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_get() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List.get [ 10, 9, 8, 7 ] 1
|
||||
"#
|
||||
),
|
||||
"Result Int [ IndexOutOfBounds ]*",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1024,7 +1024,7 @@ mod test_infer_uniq {
|
||||
\{ left, right } -> { left, right }
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr (* | a | b) { left : (Attr a c), right : (Attr b d) }* -> Attr * { left : (Attr a c), right : (Attr b d) })",
|
||||
"Attr * (Attr (* | a | b) { left : (Attr b c), right : (Attr a d) }* -> Attr * { left : (Attr b c), right : (Attr a d) })",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1066,7 +1066,7 @@ mod test_infer_uniq {
|
||||
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers?
|
||||
// i.e. the `b` could be ignored in this example, is that true in general?
|
||||
// seems like it because we don't really extract anything.
|
||||
"Attr * (Attr (* | a | b) [ Foo (Attr b c) (Attr a *) ]* -> Attr * [ Foo (Attr b c) (Attr * Str) ]*)",
|
||||
"Attr * (Attr (* | a | b) [ Foo (Attr a c) (Attr b *) ]* -> Attr * [ Foo (Attr a c) (Attr * Str) ]*)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1289,7 +1289,7 @@ mod test_infer_uniq {
|
||||
r
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)"
|
||||
"Attr * (Attr (a | b) { foo : (Attr a { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr a { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)"
|
||||
);
|
||||
}
|
||||
|
||||
@ -1307,7 +1307,7 @@ mod test_infer_uniq {
|
||||
r
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e)"
|
||||
"Attr * (Attr (a | b) { foo : (Attr a { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr a { bar : (Attr Shared c) }d) }e)"
|
||||
);
|
||||
}
|
||||
|
||||
@ -1342,7 +1342,7 @@ mod test_infer_uniq {
|
||||
r.tic.tac.toe
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | c | d) { bar : (Attr (c | d) { baz : (Attr d f) }*) }*), tic : (Attr (a | d | e) { tac : (Attr (d | e) { toe : (Attr d f) }*) }*) }* -> Attr d f)"
|
||||
"Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | c | e) { bar : (Attr (b | e) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | d) { tac : (Attr (a | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)"
|
||||
// "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (c | d | e) { bar : (Attr (c | d) { baz : (Attr c f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (a | c) { toe : (Attr c f) }*) }*) }* -> Attr c f)"
|
||||
// "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | d | e) { bar : (Attr (b | d) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (b | c) { toe : (Attr b f) }*) }*) }* -> Attr b f)"
|
||||
// "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | c | e) { bar : (Attr (b | e) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | d) { tac : (Attr (b | d) { toe : (Attr b f) }*) }*) }* -> Attr b f)"
|
||||
@ -1980,7 +1980,7 @@ mod test_infer_uniq {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\list ->
|
||||
\list ->
|
||||
p = List.get list 1
|
||||
q = List.get list 1
|
||||
|
||||
@ -1996,12 +1996,12 @@ mod test_infer_uniq {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\list ->
|
||||
\list ->
|
||||
when List.get list 0 is
|
||||
Ok v ->
|
||||
Ok v ->
|
||||
List.set list 0 (v + 1)
|
||||
|
||||
Err _ ->
|
||||
Err _ ->
|
||||
list
|
||||
"#
|
||||
),
|
||||
@ -2014,14 +2014,14 @@ mod test_infer_uniq {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\list ->
|
||||
\list ->
|
||||
if List.isEmpty list then
|
||||
list
|
||||
else
|
||||
List.set list 0 42
|
||||
"#
|
||||
),
|
||||
"Attr * (Attr (a | b) (List (Attr a Int)) -> Attr (a | b) (List (Attr a Int)))",
|
||||
"Attr * (Attr (a | b) (List (Attr b Int)) -> Attr (a | b) (List (Attr b Int)))",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user