Merge pull request #207 from rtfeldman/list-int

Basic List Int code gen in Cranelift
This commit is contained in:
Richard Feldman 2020-03-01 00:57:45 -05:00 committed by GitHub
commit 007e8340a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 146 additions and 37 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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) => {

View File

@ -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,

View File

@ -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

View File

@ -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),
}
}

View File

@ -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);
}
}
}

View File

@ -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) => {

View File

@ -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

View File

@ -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 ]*",
);
}
}

View File

@ -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)))",
);
}
}