Merging in remote

This commit is contained in:
Chad Stearns 2020-03-19 01:10:57 -04:00
commit 3984a8d60e
14 changed files with 2781 additions and 643 deletions

View file

@ -215,7 +215,7 @@ asc : Int a, Int a -> [ Eq, Lt, Gt ]
##
desc : Int a, Int a -> [ Eq, Lt, Gt ]
## TODO should we offer hash32 etc even if it has to do a hash64 and truncate?
## TODO should we offer hash32 etc even if someday it has to do a hash64 and truncate?
##
## CAUTION: This function may give different answers in future releases of Roc,
## so be aware that if you rely on the exact answer this gives today, your
@ -224,16 +224,16 @@ hash64 : a -> U64
## Limits
## The highest number that can be stored in an #Int without overflowing its
## The highest number that can be stored in an #I32 without overflowing its
## available memory and crashing.
##
## Note that this is smaller than the positive version of #Int.lowest,
## which means if you call #Num.abs on #Int.lowest, it will overflow and crash!
highest : Int *
## Note that this is smaller than the positive version of #Int.lowestI32
## which means if you call #Num.abs on #Int.lowestI32, it will overflow and crash!
highestI32 : I32
## The lowest number that can be stored in an #Int without overflowing its
## The lowest number that can be stored in an #I32 without overflowing its
## available memory and crashing.
##
## Note that the positive version of this number is this is larger than
## #Int.highest, which means if you call #Num.abs on #Int.lowest, it will overflow and crash!
lowest : Int *
## #Int.highestI32, which means if you call #Num.abs on #Int.lowestI32, it will overflow and crash!
lowest : I32

View file

@ -2,12 +2,58 @@ api Str provides Str, isEmpty, join
## Types
## A sequence of [UTF-8](https://en.wikipedia.org/wiki/UTF-8) text characters.
## A [Unicode](https://unicode.org) text value.
##
## One #Str can be up to 2 gigabytes in size. If you need to store larger
## strings than that, you can split them into smaller chunks and operate
## on those instead of on one large #Str. This often runs faster in practice,
## even for strings much smaller than 2 gigabytes.
## Dealing with text is deep topic, so by design, Roc's `Str` module sticks
## to the basics. For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point),
## see the [roc/unicode](roc/unicode) package, and for locale-specific text
## functions (including capitalization, as capitalization rules vary by locale)
## see the [roc/locale](roc/locale) package.
##
## ### Unicode
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
##
## * "Roc"
## * "鹏"
## * "🐦"
##
## Every Unicode string is a sequence of [grapheme clusters](https://unicode.org/glossary/#grapheme_cluster).
## A grapheme cluster corresponds to what a person reading a string might call
## a "character", but because the term "character" is used to mean many different
## concepts across different programming languages, we intentionally avoid it in Roc.
## Instead, we use the term "clusters" as a shorthand for "grapheme clusters."
##
## You can get the number of grapheme clusters in a string by calling `Str.countClusters` on it:
##
## >>> Str.countClusters "Roc"
##
## >>> Str.countClusters "音乐"
##
## >>> Str.countClusters "👍"
##
## > The `countClusters` function traverses the entire string to calculate its answer,
## > so it's much better for performance to use `Str.isEmpty` instead of
## > calling `Str.countClusters` and checking whether the count was `0`.
##
## ### Escape characters
##
## ### String interpolation
##
## ### Encoding
##
## Roc strings are not coupled to any particular
## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens,
## they are currently encoded in UTF-8, but this module is intentionally designed
## not to rely on that implementation detail so that a future release of Roc can
## potentially change it without breaking existing Roc applications.
##
## This module has functions to can convert a #Str to a #List of raw [code unit](https://unicode.org/glossary/#code_unit)
## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point)
## mentioned earlier) in a particular encoding. If you need encoding-specific functions,
## you should take a look at the [roc/unicode](roc/unicode) package.
## It has many more tools than this module does!
Str : [ @Str ]
## Convert
@ -21,10 +67,22 @@ Str : [ @Str ]
## but it's recommended to pass much smaller numbers instead.
##
## Passing a negative number for decimal places is equivalent to passing 0.
decimal : Int, Float -> Str
decimal : Float *, ULen -> Str
## Convert an #Int to a string.
int : Float -> Str
int : Int * -> Str
## Split a string around a separator.
##
## >>> Str.splitClusters "1,2,3" ","
##
## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list.
##
## >>> Str.splitClusters "1,2,3" ""
##
## To split a string into its grapheme clusters, use #Str.clusters
split : Str, Str -> List Str
## Check
@ -58,4 +116,196 @@ padStart : Str, Int, Str -> Str
padEnd : Str, Int, Str -> Str
## Grapheme Clusters
## Split a string into its grapheme clusters.
##
## >>> Str.clusters "1,2,3"
##
## >>> Str.clusters "👍👍👍"
##
clusters : Str -> List Str
reverseClusters : Str -> Str
foldClusters : Str, { start: state, step: (state, Str -> state) } -> state
## Returns #True if the string begins with a capital letter, and #False otherwise.
##
## >>> Str.isCapitalized "hi"
##
## >>> Str.isCapitalized "Hi"
##
## >>> Str.isCapitalized " Hi"
##
## >>> Str.isCapitalized "Česká"
##
## >>> Str.isCapitalized "Э"
##
## >>> Str.isCapitalized "東京"
##
## >>> Str.isCapitalized "🐦"
##
## >>> Str.isCapitalized ""
##
## Since the rules for how to capitalize an uncapitalized string vary by locale,
## see the [roc/locale](roc/locale) package for functions which do that.
isCapitalized : Str -> Bool
## ## Code Units
##
## Besides grapheme clusters, another way to break down strings is into
## raw code unit integers.
##
## Code units are no substitute for grapheme clusters!
## These functions exist to support advanced use cases like those found in
## [roc/unicode](roc/unicode), and using code units when grapheme clusters would
## be more appropriate can very easily lead to bugs.
##
## For example, `Str.countGraphemes "👩‍👩‍👦‍👦"` returns `1`,
## whereas `Str.toUtf8 "👩‍👩‍👦‍👦"` returns a list with a length of 25,
## `Str.toUtf16 "👩‍👩‍👦‍👦"` returns a list with a length of 11.
## and `Str.toUtf32 "👩‍👩‍👦‍👦"` returns a list with a length of 7.
## Return a #List of the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a #List of smaller #Str values instead of #U8 values,
## see #Str.split and #Str.clusters.)
##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf8 "Roc"
##
## >>> Str.toUtf8 "鹏"
##
## >>> Str.toUtf8 "🐦"
##
## For a more flexible function that walks through each of these #U8 code units
## without creating a #List, see #Str.foldUtf8 and #Str.foldRevUtf8.
toUtf8 : Str -> List U8
## Return a #List of the string's #U16 UTF-16 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a #List of smaller #Str values instead of #U16 values,
## see #Str.split and #Str.clusters.)
##
## >>> Str.toUtf16 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf16 "Roc"
##
## >>> Str.toUtf16 "鹏"
##
## >>> Str.toUtf16 "🐦"
##
## For a more flexible function that walks through each of these #U16 code units
## without creating a #List, see #Str.foldUtf16 and #Str.foldRevUtf16.
toUtf16 : Str -> List U16
## Return a #List of the string's #U32 UTF-32 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a #List of smaller #Str values instead of #U32 values,
## see #Str.split and #Str.clusters.)
##
## >>> Str.toUtf32 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf32 "Roc"
##
## >>> Str.toUtf32 "鹏"
##
## >>> Str.toUtf32 "🐦"
##
## For a more flexible function that walks through each of these #U32 code units
## without creating a #List, see #Str.foldUtf32 and #Str.foldRevUtf32.
toUtf32 : Str -> List U32
## Walk through the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## to build up a state.
## (If you want a `step` function which receives a #Str instead of an #U8, see #Str.foldClusters.)
##
## Here are the #U8 values that will be passed to `step` when this function is
## called on various strings:
##
## * `"👩‍👩‍👦‍👦"` passes 240, 159, 145, 169, 226, 128, 141, 240, 159, 145, 169, 226, 128, 141, 240, 159, 145, 166, 226, 128, 141, 240, 159, 145, 166
## * `"Roc"` passes 82, 111, 99
## * `"鹏"` passes 233, 185, 143
## * `"🐦"` passes 240, 159, 144, 166
##
## To convert a #Str into a plain `List U8` of UTF-8 code units, see #Str.toUtf8.
foldUtf8 : Str, { start: state, step: (state, U8 -> state) } -> state
## Walk through the string's #U16 UTF-16 [code units](https://unicode.org/glossary/#code_unit)
## to build up a state.
## (If you want a `step` function which receives a #Str instead of an #U16, see #Str.foldClusters.)
##
## Here are the #U16 values that will be passed to `step` when this function is
## called on various strings:
##
## * `"👩‍👩‍👦‍👦"` passes 55357, 56425, 8205, 55357, 56425, 8205, 55357, 56422, 8205, 55357, 56422
## * `"Roc"` passes 82, 111, 99
## * `"鹏"` passes 40527
## * `"🐦"` passes 55357, 56358
##
## To convert a #Str into a plain `List U16` of UTF-16 code units, see #Str.toUtf16.
foldUtf16 : Str, { start: state, step: (state, U16 -> state) } -> state
## Walk through the string's #U32 UTF-32 [code units](https://unicode.org/glossary/#code_unit)
## to build up a state.
## (If you want a `step` function which receives a #Str instead of an #U32, see #Str.foldClusters.)
##
## Here are the #U32 values that will be passed to `step` when this function is
## called on various strings:
##
## * `"👩‍👩‍👦‍👦"` passes 128105, 8205, 128105, 8205, 128102, 8205, 128102
## * `"Roc"` passes 82, 111, 99
## * `"鹏"` passes 40527
## * `"🐦"` passes 128038
##
## To convert a #Str into a plain `List U32` of UTF-32 code units, see #Str.toUtf32.
foldUtf32 : Str, { start: state, step: (state, U32 -> state) } -> state
## Walk backwards through the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## to build up a state.
## (If you want a `step` function which receives a #Str instead of an #U8, see #Str.foldClusters.)
##
## Here are the #U8 values that will be passed to `step` when this function is
## called on various strings:
##
## * `"👩‍👩‍👦‍👦"` passes 166, 145, 159, 240, 141, 128, 226, 166, 145, 159, 240, 141, 128, 226, 169, 145, 159, 240, 141, 128, 226, 169, 145, 159, 240
## * `"Roc"` passes 99, 111, 82
## * `"鹏"` passes 143, 185, 233
## * `"🐦"` passes 166, 144, 159, 240
##
## To convert a #Str into a plain `List U8` of UTF-8 code units, see #Str.toUtf8.
foldRevUtf8 : Str, { start: state, step: (state, U8 -> state) } -> state
## Walk backwards through the string's #U16 UTF-16 [code units](https://unicode.org/glossary/#code_unit)
## to build up a state.
## (If you want a `step` function which receives a #Str instead of an #U16, see #Str.foldClusters.)
##
## Here are the #U16 values that will be passed to `step` when this function is
## called on various strings:
##
## * `"👩‍👩‍👦‍👦"` passes 56422, 55357, 8205, 56422, 55357, 8205, 56425, 55357, 8205, 56425, 55357
## * `"Roc"` passes 99, 111, 82
## * `"鹏"` passes 40527
## * `"🐦"` passes 56358, 55357
##
## To convert a #Str into a plain `List U16` of UTF-16 code units, see #Str.toUtf16.
foldRevUtf16 : Str, { start: state, step: (state, U16 -> state) } -> state
## Walk backwards through the string's #U32 UTF-32 [code units](https://unicode.org/glossary/#code_unit)
## to build up a state.
## (If you want a `step` function which receives a #Str instead of an #U32, see #Str.foldClusters.)
##
## Here are the #U32 values that will be passed to `step` when this function is
## called on various strings:
##
## * `"👩‍👩‍👦‍👦"` passes 128102, 8205, 128102, 8205, 128105, 8205, 128105
## * `"Roc"` passes 99, 111, 82
## * `"鹏"` passes 40527
## * `"🐦"` passes 128038
##
## To convert a #Str into a plain `List U32` of UTF-32 code units, see #Str.toUtf32.
foldRevUtf32 : Str, { start: state, step: (state, U32 -> state) } -> state

View file

@ -55,6 +55,8 @@ pub fn build_expr<'a, B: Backend>(
) -> Value {
use roc_mono::expr::Expr::*;
let ptr_bytes = env.cfg.pointer_bytes() as u32;
match expr {
Int(num) => builder.ins().iconst(types::I64, *num),
Float(num) => builder.ins().f64const(*num),
@ -173,7 +175,6 @@ pub fn build_expr<'a, B: Backend>(
},
Struct(sorted_fields) => {
let cfg = env.cfg;
let ptr_bytes = cfg.pointer_bytes() as u32;
// The slot size will be the sum of all the fields' sizes
let mut slot_size = 0;
@ -189,6 +190,7 @@ pub fn build_expr<'a, B: Backend>(
));
// Create instructions for storing each field's expression
// NOTE assumes that all fields have the same width!
for (index, (field_expr, field_layout)) in sorted_fields.iter().enumerate() {
let val = build_expr(env, &scope, module, builder, field_expr, procs);
@ -203,6 +205,50 @@ pub fn build_expr<'a, B: Backend>(
.ins()
.stack_addr(cfg.pointer_type(), slot, Offset32::new(0))
}
Tag { tag_layout, arguments , tag_id, union_size, .. } => {
let cfg = env.cfg;
let ptr_bytes = cfg.pointer_bytes() as u32;
// NOTE: all variants of a tag union must have the same size, so (among other things)
// it's easy to quickly index them in arrays. Therefore the size of this tag doens't
// depend on the tag arguments, but solely on the layout of the whole tag union
let slot_size = tag_layout.stack_size(ptr_bytes);
// Create a slot
let slot = builder.create_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
slot_size
));
// Create instructions for storing each field's expression
let mut offset = 0;
// still need to insert the tag discriminator for non-single unions
// when there are no arguments, e.g. `Nothing : Maybe a`
if *union_size > 1 {
let val = builder.ins().iconst(types::I64, *tag_id as i64);
builder.ins().stack_store(val, slot, Offset32::new(0));
offset += ptr_bytes;
}
for (field_expr, field_layout) in arguments.iter() {
let val = build_expr(env, &scope, module, builder, field_expr, procs);
let field_size = field_layout.stack_size(ptr_bytes);
let field_offset = i32::try_from(offset)
.expect("TODO handle field size conversion to i32");
builder.ins().stack_store(val, slot, Offset32::new(field_offset));
offset += field_size;
}
builder
.ins()
.stack_addr(cfg.pointer_type(), slot, Offset32::new(0))
}
Access {
label,
field_layout,
@ -252,15 +298,49 @@ pub fn build_expr<'a, B: Backend>(
.ins()
.load(cfg.pointer_type(), mem_flags, record, Offset32::new(offset))
}
AccessAtIndex {
index,
field_layouts,
expr,
..
} => {
let cfg = env.cfg;
let mut offset = 0;
for (field_index, field_layout) in field_layouts.iter().enumerate() {
if *index == field_index as u64 {
let offset = i32::try_from(offset)
.expect("TODO gracefully handle usize -> i32 conversion in struct access");
let mem_flags = MemFlags::new();
let expr = build_expr(env, scope, module, builder, expr, procs);
let ret_type = layout_to_type(&field_layout, cfg.pointer_type());
return builder
.ins()
.load(ret_type, mem_flags, expr, Offset32::new(offset));
}
offset += field_layout.stack_size(ptr_bytes);
}
panic!("field access out of bounds: index {:?} in layouts {:?}", index, field_layouts)
}
Str(str_literal) => {
if str_literal.is_empty() {
panic!("TODO build an empty string in Crane");
} else {
let bytes_len = str_literal.len() + 1/* TODO drop the +1 when we have structs and this is no longer a NUL-terminated CString.*/;
let ptr = call_malloc(env, module, builder, bytes_len);
let size = builder.ins().iconst(types::I64, bytes_len as i64);
let ptr = call_malloc(env, module, builder, size);
let mem_flags = MemFlags::new();
// Copy the bytes from the string literal into the array
// Store the bytes from the string literal in the array
for (index, byte) in str_literal.bytes().enumerate() {
let val = builder.ins().iconst(types::I8, byte as i64);
let offset = Offset32::new(index as i32);
@ -294,7 +374,8 @@ pub fn build_expr<'a, B: Backend>(
} else {
let elem_bytes = elem_layout.stack_size(ptr_bytes as u32);
let bytes_len = elem_bytes as usize * elems.len();
let elems_ptr = call_malloc(env, module, builder, bytes_len);
let size = builder.ins().iconst(types::I64, bytes_len as i64);
let elems_ptr = call_malloc(env, module, builder, size);
let mem_flags = MemFlags::new();
// Copy the elements from the literal into the array
@ -308,18 +389,19 @@ pub fn build_expr<'a, B: Backend>(
elems_ptr
};
// Store the pointer in slot 0
builder
.ins()
.stack_store(elems_ptr, slot, Offset32::new(0));
// Store the pointer
{
let offset = Offset32::new((Builtin::WRAPPER_PTR * ptr_bytes) as i32);
// Store the length in slot 1
builder.ins().stack_store(elems_ptr, slot, offset);
}
// Store the length
{
let length = builder.ins().iconst(env.ptr_sized_int(), elems.len() as i64);
let offset = Offset32::new((Builtin::WRAPPER_LEN * ptr_bytes) as i32);
builder
.ins()
.stack_store(length, slot, Offset32::new(ptr_bytes as i32));
builder.ins().stack_store(length, slot, offset);
}
// Return the pointer to the wrapper
@ -331,6 +413,21 @@ pub fn build_expr<'a, B: Backend>(
}
}
fn layout_to_type<'a>(layout: &Layout<'a>, _pointer_type: Type) -> Type {
use roc_mono::layout::Builtin::*;
match layout {
Layout::Builtin(builtin) => match builtin {
Int64 => cranelift::prelude::types::I64,
Byte => cranelift::prelude::types::I8,
Bool => cranelift::prelude::types::B1,
Float64 => cranelift::prelude::types::F64,
other => panic!("I don't yet know how to make a type from {:?}", other),
},
other => panic!("I don't yet know how to make a type from {:?}", other),
}
}
struct Branch2<'a> {
cond: &'a Expr<'a>,
cond_layout: &'a Layout<'a>,
@ -363,7 +460,7 @@ fn build_branch2<'a, B: Backend>(
let fail_block = builder.create_block();
match branch.cond_layout {
Layout::Builtin(Builtin::Bool(_, _)) => {
Layout::Builtin(Builtin::Bool) => {
builder.ins().brnz(cond, pass_block, &[]);
}
other => panic!("I don't know how to build a conditional for {:?}", other),
@ -645,14 +742,13 @@ fn call_by_name<'a, B: Backend>(
debug_assert!(args.len() == 1);
let list_ptr = build_arg(&args[0], env, scope, module, builder, procs);
let ptr_bytes = env.cfg.pointer_bytes() as u32;
let offset = Offset32::new((Builtin::WRAPPER_LEN * ptr_bytes) as i32);
// Get the usize int length
builder.ins().load(
env.ptr_sized_int(),
MemFlags::new(),
list_ptr,
Offset32::new(env.cfg.pointer_bytes() as i32),
)
// Get the usize list length
builder
.ins()
.load(env.ptr_sized_int(), MemFlags::new(), list_ptr, offset)
}
Symbol::INT_EQ_I64 | Symbol::INT_EQ_I8 | Symbol::INT_EQ_I1 => {
debug_assert!(args.len() == 2);
@ -674,68 +770,68 @@ fn call_by_name<'a, B: Backend>(
let wrapper_ptr = build_arg(&args[0], env, scope, module, builder, procs);
let elem_index = build_arg(&args[1], env, scope, module, builder, procs);
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);
// Load the pointer we got to the wrapper struct
let elems_ptr = builder.ins().load(
env.cfg.pointer_type(),
MemFlags::new(),
wrapper_ptr,
Offset32::new(0),
);
// Multiply the requested index by the size of each element.
let offset = builder.ins().imul(elem_index, elem_size);
// Follow the pointer in the wrapper struct to the actual elements
builder.ins().load_complex(
elem_type,
MemFlags::new(),
&[elems_ptr, offset],
Offset32::new(0),
)
}
Symbol::LIST_SET => {
let (_list_expr, list_layout) = &args[0];
// Get the usize list length
let ptr_bytes = env.cfg.pointer_bytes() as u32;
let offset = Offset32::new((Builtin::WRAPPER_LEN * ptr_bytes) as i32);
let _list_len =
builder
.ins()
.load(env.ptr_sized_int(), MemFlags::new(), wrapper_ptr, offset);
// TODO compare elem_index to _list_len to do array bounds checking.
match list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => {
// TODO try memcpy for shallow clones; it's probably faster
// let list_val = build_expr(env, scope, module, builder, list_expr, procs);
let cfg = env.cfg;
let elem_type = type_from_layout(cfg, elem_layout);
let elem_bytes = elem_layout.stack_size(cfg.pointer_bytes() as u32);
let elem_size = builder.ins().iconst(types::I64, elem_bytes as i64);
let num_elems = 10; // TODO FIXME read from List.len
let elem_bytes =
elem_layout.stack_size(env.cfg.pointer_bytes() as u32) as usize;
let bytes_len = (elem_bytes * num_elems) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */;
let wrapper_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);
// Load the pointer we got to the wrapper struct
let _elems_ptr = builder.ins().load(
let elems_ptr = builder.ins().load(
env.cfg.pointer_type(),
MemFlags::new(),
wrapper_ptr,
Offset32::new(0),
);
// Multiply the requested index by the size of each element.
let offset = builder.ins().imul(elem_index, elem_size);
// Follow the pointer in the wrapper struct to the actual elements
builder.ins().load_complex(
elem_type,
MemFlags::new(),
&[elems_ptr, offset],
Offset32::new(0),
)
}
_ => {
unreachable!("Invalid List layout for List.get: {:?}", list_layout);
}
}
}
Symbol::LIST_SET => {
// set : List elem, Int, elem -> List elem
let wrapper_ptr = build_arg(&args[0], env, scope, module, builder, procs);
let (_list_expr, list_layout) = &args[0];
// Get the usize list length
let ptr_bytes = env.cfg.pointer_bytes() as u32;
let offset = Offset32::new((Builtin::WRAPPER_LEN * ptr_bytes) as i32);
let _list_len =
builder
.ins()
.load(env.ptr_sized_int(), MemFlags::new(), wrapper_ptr, offset);
// TODO do array bounds checking, and early return the original List if out of bounds
match list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => {
let wrapper_ptr = clone_list(env, builder, module, wrapper_ptr, elem_layout);
list_set_in_place(
env,
wrapper_ptr,
@ -756,6 +852,17 @@ fn call_by_name<'a, B: Backend>(
// set : List elem, Int, elem -> List elem
debug_assert!(args.len() == 3);
// Get the usize list length
let wrapper_ptr = build_arg(&args[0], env, scope, module, builder, procs);
let ptr_bytes = env.cfg.pointer_bytes() as u32;
let offset = Offset32::new((Builtin::WRAPPER_LEN * ptr_bytes) as i32);
let _list_len =
builder
.ins()
.load(env.ptr_sized_int(), MemFlags::new(), wrapper_ptr, offset);
// TODO do array bounds checking, and early return the original List if out of bounds
let (list_expr, list_layout) = &args[0];
let list_val = build_expr(env, scope, module, builder, list_expr, procs);
@ -802,17 +909,13 @@ fn call_malloc<B: Backend>(
env: &Env<'_>,
module: &mut Module<B>,
builder: &mut FunctionBuilder,
size: usize,
size: Value,
) -> Value {
// Declare malloc inside this function
let local_func = module.declare_func_in_func(env.malloc, &mut builder.func);
// Convert the size argument to a Value
let ptr_size_type = module.target_config().pointer_type();
let size_arg = builder.ins().iconst(ptr_size_type, size as i64);
// Call malloc and return the resulting pointer
let call = builder.ins().call(local_func, &[size_arg]);
let call = builder.ins().call(local_func, &[size]);
let results = builder.inst_results(call);
debug_assert!(results.len() == 1);
@ -850,3 +953,77 @@ fn list_set_in_place<'a>(
wrapper_ptr
}
fn clone_list<B: Backend>(
env: &Env<'_>,
builder: &mut FunctionBuilder,
module: &mut Module<B>,
src_wrapper_ptr: Value,
elem_layout: &Layout<'_>,
) -> Value {
let cfg = env.cfg;
let ptr_bytes = env.cfg.pointer_bytes() as u32;
// Load the pointer we got to the wrapper struct
let elems_ptr = {
let offset = Offset32::new((Builtin::WRAPPER_PTR * ptr_bytes) as i32);
builder
.ins()
.load(cfg.pointer_type(), MemFlags::new(), src_wrapper_ptr, offset)
};
// Get the usize list length
let list_len = {
let offset = Offset32::new((Builtin::WRAPPER_LEN * ptr_bytes) as i32);
builder.ins().load(
env.ptr_sized_int(),
MemFlags::new(),
src_wrapper_ptr,
offset,
)
};
// Calculate the number of bytes we'll need to allocate.
let elem_bytes = builder.ins().iconst(
env.ptr_sized_int(),
elem_layout.stack_size(cfg.pointer_bytes() as u32) as i64,
);
let size = builder.ins().imul(elem_bytes, list_len);
// Allocate space for the new array that we'll copy into.
let new_elems_ptr = call_malloc(env, module, builder, size);
// Either memcpy or deep clone the array elements
if elem_layout.safe_to_memcpy() {
// Copy the bytes from the original array into the new
// one we just malloc'd.
//
// TODO how do we decide when to do the small memcpy vs the normal one?
builder.call_memcpy(env.cfg, new_elems_ptr, elems_ptr, size);
} else {
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
}
// Create a fresh wrapper struct for the newly populated array
let ptr_bytes = cfg.pointer_bytes() as u32;
let slot = builder.create_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
ptr_bytes * Builtin::LIST_WORDS,
));
// Store the new pointer in slot 0 of the wrapper
builder
.ins()
.stack_store(new_elems_ptr, slot, Offset32::new(0));
// Store the length in slot 1 of the wrapper
builder
.ins()
.stack_store(list_len, slot, Offset32::new(ptr_bytes as i32));
builder
.ins()
.stack_addr(cfg.pointer_type(), slot, Offset32::new(0))
}

View file

@ -10,12 +10,12 @@ pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>) -> Type
use roc_mono::layout::Layout::*;
match layout {
Pointer(_) | FunctionPointer(_, _) | Struct(_) | Tag(_) => cfg.pointer_type(),
FunctionPointer(_, _) | Struct(_) | Union(_) => cfg.pointer_type(),
Builtin(builtin) => match builtin {
Int64 => types::I64,
Float64 => types::F64,
Bool(_, _) => types::B1,
Byte(_) => types::I8,
Bool => types::B1,
Byte => types::I8,
Str | EmptyStr | Map(_, _) | EmptyMap | Set(_) | EmptySet | List(_) | EmptyList => {
cfg.pointer_type()
}

View file

@ -3,13 +3,13 @@ use bumpalo::Bump;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::types::BasicTypeEnum;
use inkwell::types::{BasicTypeEnum, IntType};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{FunctionValue, IntValue, PointerValue};
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use crate::llvm::convert::{
basic_type_from_layout, collection_wrapper, get_array_type, get_fn_type,
basic_type_from_layout, collection_wrapper, get_array_type, get_fn_type, ptr_int,
};
use roc_collections::all::ImMap;
use roc_module::symbol::{Interns, Symbol};
@ -32,7 +32,13 @@ pub struct Env<'a, 'ctx, 'env> {
pub builder: &'env Builder<'ctx>,
pub module: &'ctx Module<'ctx>,
pub interns: Interns,
pub pointer_bytes: u32,
pub ptr_bytes: u32,
}
impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
pub fn ptr_int(&self) -> IntType<'ctx> {
ptr_int(self.context, self.ptr_bytes)
}
}
pub fn build_expr<'a, 'ctx, 'env>(
@ -75,7 +81,8 @@ pub fn build_expr<'a, 'ctx, 'env>(
ret_layout,
cond_layout,
} => {
let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout);
let ret_type =
basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
let switch_args = SwitchArgs {
cond_layout: cond_layout.clone(),
cond_expr: cond,
@ -92,7 +99,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
for (symbol, layout, expr) in stores.iter() {
let val = build_expr(env, &scope, parent, &expr, procs);
let expr_bt = basic_type_from_layout(env.arena, context, &layout);
let expr_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes);
let alloca = create_entry_block_alloca(
env,
parent,
@ -122,14 +129,14 @@ pub fn build_expr<'a, 'ctx, 'env>(
panic!("TODO create a phi node for &&");
}
_ => {
let mut arg_vals: Vec<BasicValueEnum> =
let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> =
Vec::with_capacity_in(args.len(), env.arena);
for (arg, _layout) in args.iter() {
arg_vals.push(build_expr(env, scope, parent, arg, procs));
for (arg, layout) in args.iter() {
arg_tuples.push((build_expr(env, scope, parent, arg, procs), layout));
}
call_with_args(*symbol, arg_vals.into_bump_slice(), env)
call_with_args(*symbol, arg_tuples.into_bump_slice(), env)
}
},
FunctionPointer(symbol) => {
@ -165,7 +172,6 @@ pub fn build_expr<'a, 'ctx, 'env>(
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
}
Load(symbol) => match scope.get(symbol) {
Some((_, ptr)) => env
.builder
@ -209,13 +215,13 @@ pub fn build_expr<'a, 'ctx, 'env>(
}
Array { elem_layout, elems } => {
let ctx = env.context;
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout);
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let builder = env.builder;
if elems.is_empty() {
let array_type = get_array_type(&elem_type, 0);
let ptr_type = array_type.ptr_type(AddressSpace::Generic);
let struct_type = collection_wrapper(ctx, ptr_type);
let struct_type = collection_wrapper(ctx, ptr_type, env.ptr_bytes);
// The first field in the struct should be the pointer.
let struct_val = builder
@ -230,11 +236,12 @@ pub fn build_expr<'a, 'ctx, 'env>(
BasicValueEnum::StructValue(struct_val.into_struct_value())
} else {
let len_u64 = elems.len() as u64;
let elem_bytes = elem_layout.stack_size(env.pointer_bytes) as u64;
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let ptr = {
let bytes_len = elem_bytes * len_u64;
let len = ctx.i32_type().const_int(bytes_len, false);
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
env.builder
.build_array_malloc(elem_type, len, "create_list_ptr")
@ -252,8 +259,8 @@ pub fn build_expr<'a, 'ctx, 'env>(
}
let ptr_val = BasicValueEnum::PointerValue(ptr);
let struct_type = collection_wrapper(ctx, ptr.get_type());
let len = BasicValueEnum::IntValue(ctx.i32_type().const_int(len_u64, false));
let struct_type = collection_wrapper(ctx, ptr.get_type(), env.ptr_bytes);
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
let mut struct_val;
// Field 0: pointer
@ -271,16 +278,6 @@ pub fn build_expr<'a, 'ctx, 'env>(
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
// Field 2: capacity (initially set to length)
struct_val = builder
.build_insert_value(
struct_val,
len,
Builtin::WRAPPER_CAPACITY,
"insert_capacity",
)
.unwrap();
BasicValueEnum::StructValue(struct_val.into_struct_value())
}
}
@ -296,7 +293,8 @@ pub fn build_expr<'a, 'ctx, 'env>(
for (field_expr, field_layout) in sorted_fields.iter() {
let val = build_expr(env, &scope, parent, field_expr, procs);
let field_type = basic_type_from_layout(env.arena, env.context, &field_layout);
let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
field_types.push(field_type);
field_vals.push(val);
@ -315,6 +313,157 @@ pub fn build_expr<'a, 'ctx, 'env>(
BasicValueEnum::StructValue(struct_val.into_struct_value())
}
Tag {
union_size,
arguments,
..
} if *union_size == 1 => {
let it = arguments.iter();
let ctx = env.context;
let builder = env.builder;
// Determine types
let num_fields = arguments.len() + 1;
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for (field_expr, field_layout) in it {
let val = build_expr(env, &scope, parent, field_expr, procs);
let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
field_types.push(field_type);
field_vals.push(val);
}
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let mut struct_val = struct_type.const_zero().into();
// Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder
.build_insert_value(struct_val, field_val, index as u32, "insert_field")
.unwrap();
}
BasicValueEnum::StructValue(struct_val.into_struct_value())
}
Tag {
arguments,
tag_layout,
union_size,
tag_id,
..
} => {
let ptr_size = env.ptr_bytes;
let whole_size = tag_layout.stack_size(ptr_size);
let mut filler = tag_layout.stack_size(ptr_size);
let ctx = env.context;
let builder = env.builder;
// Determine types
let num_fields = arguments.len() + 1;
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
// insert the discriminant value
if *union_size > 1 {
let val = env
.context
.i64_type()
.const_int(*tag_id as u64, true)
.into();
let field_type = env.context.i64_type().into();
field_types.push(field_type);
field_vals.push(val);
let field_size = ptr_size;
filler -= field_size;
}
for (field_expr, field_layout) in arguments.iter() {
let val = build_expr(env, &scope, parent, field_expr, procs);
let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size);
field_types.push(field_type);
field_vals.push(val);
let field_size = field_layout.stack_size(ptr_size);
filler -= field_size;
}
// TODO verify that this is required (better safe than sorry)
if filler > 0 {
field_types.push(env.context.i8_type().array_type(filler).into());
}
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let mut struct_val = struct_type.const_zero().into();
// Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder
.build_insert_value(struct_val, field_val, index as u32, "insert_field")
.unwrap();
}
// How we create tag values
//
// The memory layout of tags can be different. e.g. in
//
// [ Ok Int, Err Str ]
//
// the `Ok` tag stores a 64-bit integer, the `Err` tag stores a struct.
// All tags of a union must have the same length, for easy addressing (e.g. array lookups).
// So we need to ask for the maximum of all tag's sizes, even if most tags won't use
// all that memory, and certainly won't use it in the same way (the tags have fields of
// different types/sizes)
//
// In llvm, we must be explicit about the type of value we're creating: we can't just
// make a unspecified block of memory. So what we do is create a byte array of the
// desired size. Then when we know which tag we have (which is here, in this function),
// we need to cast that down to the array of bytes that llvm expects
//
// There is the bitcast instruction, but it doesn't work for arrays. So we need to jump
// through some hoops using store and load to get this to work: the array is put into a
// one-element struct, which can be cast to the desired type.
//
// This tricks comes from
// https://github.com/raviqqe/ssf/blob/bc32aae68940d5bddf5984128e85af75ca4f4686/ssf-llvm/src/expression_compiler.rs#L116
let array_type = ctx.i8_type().array_type(whole_size);
let struct_pointer = builder.build_alloca(array_type, "struct_poitner");
builder.build_store(
builder
.build_bitcast(
struct_pointer,
struct_type.ptr_type(inkwell::AddressSpace::Generic),
"",
)
.into_pointer_value(),
struct_val,
);
let result = builder.build_load(struct_pointer, "");
// For unclear reasons, we can't cast an array to a struct on the other side.
// the solution is to wrap the array in a struct (yea...)
let wrapper_type = ctx.struct_type(&[array_type.into()], false);
let mut wrapper_val = wrapper_type.const_zero().into();
wrapper_val = builder
.build_insert_value(wrapper_val, result, 0, "insert_field")
.unwrap();
wrapper_val.into_struct_value().into()
}
Access {
label,
field_layout,
@ -348,6 +497,73 @@ pub fn build_expr<'a, 'ctx, 'env>(
.build_extract_value(struct_val, index, "field_access")
.unwrap()
}
AccessAtIndex {
index,
expr,
is_unwrapped,
..
} if *is_unwrapped => {
let builder = env.builder;
// Get Struct val
// Since this is a one-element tag union, we get the correct struct immediately
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value();
builder
.build_extract_value(
argument,
*index as u32,
env.arena.alloc(format!("tag_field_access_{}_", index)),
)
.unwrap()
}
AccessAtIndex {
index,
expr,
field_layouts,
..
} => {
let builder = env.builder;
// Determine types, assumes the descriminant is in the field layouts
let num_fields = field_layouts.len();
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let ptr_bytes = env.ptr_bytes;
for field_layout in field_layouts.iter() {
let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, ptr_bytes);
field_types.push(field_type);
}
// Create the struct_type
let struct_type = env
.context
.struct_type(field_types.into_bump_slice(), false);
// cast the argument bytes into the desired shape for this tag
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value();
let argument_pointer = builder.build_alloca(argument.get_type(), "");
builder.build_store(argument_pointer, argument);
let argument = builder
.build_load(
builder
.build_bitcast(
argument_pointer,
struct_type.ptr_type(inkwell::AddressSpace::Generic),
"",
)
.into_pointer_value(),
"",
)
.into_struct_value();
builder
.build_extract_value(argument, *index as u32, "")
.expect("desired field did not decode")
}
_ => {
panic!("I don't yet know how to LLVM build {:?}", expr);
}
@ -369,7 +585,7 @@ fn build_branch2<'a, 'ctx, 'env>(
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> {
let ret_layout = cond.ret_layout;
let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout);
let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
let cond_expr = build_expr(env, scope, parent, cond.cond, procs);
@ -433,10 +649,8 @@ fn build_switch<'a, 'ctx, 'env>(
// they either need to all be i8, or i64
let int_val = match cond_layout {
Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Bool(_, _)) => {
context.bool_type().const_int(*int as u64, false)
}
Layout::Builtin(Builtin::Byte(_)) => context.i8_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Bool) => context.bool_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Byte) => context.i8_type().const_int(*int as u64, false),
_ => panic!("Can't cast to cond_layout = {:?}", cond_layout),
};
let block = context.append_basic_block(parent, format!("branch{}", int).as_str());
@ -566,12 +780,12 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
let args = proc.args;
let arena = env.arena;
let context = &env.context;
let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout);
let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout, env.ptr_bytes);
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
let mut arg_symbols = Vec::new_in(arena);
for (layout, arg_symbol) in args.iter() {
let arg_type = basic_type_from_layout(arena, env.context, &layout);
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
arg_basic_types.push(arg_type);
arg_symbols.push(arg_symbol);
@ -639,7 +853,7 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
#[allow(clippy::cognitive_complexity)]
fn call_with_args<'a, 'ctx, 'env>(
symbol: Symbol,
args: &[BasicValueEnum<'ctx>],
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
env: &Env<'a, 'ctx, 'env>,
) -> BasicValueEnum<'ctx> {
match symbol {
@ -647,8 +861,8 @@ fn call_with_args<'a, 'ctx, 'env>(
debug_assert!(args.len() == 2);
let int_val = env.builder.build_int_add(
args[0].into_int_value(),
args[1].into_int_value(),
args[0].0.into_int_value(),
args[1].0.into_int_value(),
"add_i64",
);
@ -658,8 +872,8 @@ fn call_with_args<'a, 'ctx, 'env>(
debug_assert!(args.len() == 2);
let float_val = env.builder.build_float_add(
args[0].into_float_value(),
args[1].into_float_value(),
args[0].0.into_float_value(),
args[1].0.into_float_value(),
"add_f64",
);
@ -669,8 +883,8 @@ fn call_with_args<'a, 'ctx, 'env>(
debug_assert!(args.len() == 2);
let int_val = env.builder.build_int_sub(
args[0].into_int_value(),
args[1].into_int_value(),
args[0].0.into_int_value(),
args[1].0.into_int_value(),
"sub_I64",
);
@ -680,8 +894,8 @@ fn call_with_args<'a, 'ctx, 'env>(
debug_assert!(args.len() == 2);
let float_val = env.builder.build_float_sub(
args[0].into_float_value(),
args[1].into_float_value(),
args[0].0.into_float_value(),
args[1].0.into_float_value(),
"sub_f64",
);
@ -691,8 +905,8 @@ fn call_with_args<'a, 'ctx, 'env>(
debug_assert!(args.len() == 2);
let int_val = env.builder.build_int_mul(
args[0].into_int_value(),
args[1].into_int_value(),
args[0].0.into_int_value(),
args[1].0.into_int_value(),
"mul_i64",
);
@ -703,29 +917,26 @@ fn call_with_args<'a, 'ctx, 'env>(
let int_val = env
.builder
.build_int_neg(args[0].into_int_value(), "negate_i64");
.build_int_neg(args[0].0.into_int_value(), "negate_i64");
BasicValueEnum::IntValue(int_val)
}
Symbol::LIST_LEN => {
debug_assert!(args.len() == 1);
let wrapper_struct = args[0].into_struct_value();
let wrapper_struct = args[0].0.into_struct_value();
let builder = env.builder;
// Get the 32-bit int length
let i32_val = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "unwrapped_list_len").unwrap().into_int_value();
// cast the 32-bit length to a 64-bit int
BasicValueEnum::IntValue(builder.build_int_cast(i32_val, env.context.i64_type(), "i32_to_i64"))
// Get the usize int length
builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "unwrapped_list_len").unwrap().into_int_value().into()
}
Symbol::LIST_IS_EMPTY => {
debug_assert!(args.len() == 1);
let list_struct = args[0].into_struct_value();
let list_struct = args[0].0.into_struct_value();
let builder = env.builder;
let list_len = builder.build_extract_value(list_struct, 1, "unwrapped_list_len").unwrap().into_int_value();
let zero = env.context.i32_type().const_zero();
let zero = env.ptr_int().const_zero();
let answer = builder.build_int_compare(IntPredicate::EQ, list_len, zero, "is_zero");
BasicValueEnum::IntValue(answer)
@ -735,8 +946,8 @@ fn call_with_args<'a, 'ctx, 'env>(
let int_val = env.builder.build_int_compare(
IntPredicate::EQ,
args[0].into_int_value(),
args[1].into_int_value(),
args[0].0.into_int_value(),
args[1].0.into_int_value(),
"cmp_i64",
);
@ -747,8 +958,8 @@ fn call_with_args<'a, 'ctx, 'env>(
let int_val = env.builder.build_int_compare(
IntPredicate::EQ,
args[0].into_int_value(),
args[1].into_int_value(),
args[0].0.into_int_value(),
args[1].0.into_int_value(),
"cmp_i1",
);
@ -759,8 +970,8 @@ fn call_with_args<'a, 'ctx, 'env>(
let int_val = env.builder.build_int_compare(
IntPredicate::EQ,
args[0].into_int_value(),
args[1].into_int_value(),
args[0].0.into_int_value(),
args[1].0.into_int_value(),
"cmp_i8",
);
@ -771,8 +982,8 @@ fn call_with_args<'a, 'ctx, 'env>(
let int_val = env.builder.build_float_compare(
FloatPredicate::OEQ,
args[0].into_float_value(),
args[1].into_float_value(),
args[0].0.into_float_value(),
args[1].0.into_float_value(),
"cmp_f64",
);
@ -784,36 +995,45 @@ fn call_with_args<'a, 'ctx, 'env>(
// List.get : List elem, Int -> Result elem [ OutOfBounds ]*
debug_assert!(args.len() == 2);
let wrapper_struct = args[0].into_struct_value();
let elem_index = args[1].into_int_value();
let (_list_expr, list_layout) = &args[0];
// Slot 1 in the wrapper struct is the length
let wrapper_struct = args[0].0.into_struct_value();
let elem_index = args[1].0.into_int_value();
// Get the length from the wrapper struct
let _list_len = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "unwrapped_list_len").unwrap().into_int_value();
// TODO here, check to see if the requested index exceeds the length of the array.
// Slot 0 in the wrapper struct is the pointer to the array data
let array_data_ptr = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "unwrapped_list_ptr").unwrap().into_pointer_value();
match list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => {
// Get the pointer to the array data
let array_data_ptr = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "unwrapped_list_ptr").unwrap().into_pointer_value();
let elem_bytes = 8; // TODO Look this size up instead of hardcoding it!
let elem_size = env.context.i64_type().const_int(elem_bytes, false);
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let elem_size = env.context.i64_type().const_int(elem_bytes, false);
// Calculate the offset at runtime by multiplying the index by the size of an element.
let offset_bytes = builder.build_int_mul(elem_index, elem_size, "mul_offset");
// Calculate the offset at runtime by multiplying the index by the size of an element.
let offset_bytes = builder.build_int_mul(elem_index, elem_size, "mul_offset");
// We already checked the bounds earlier.
let elem_ptr = unsafe { builder.build_in_bounds_gep(array_data_ptr, &[offset_bytes], "elem") };
// We already checked the bounds earlier.
let elem_ptr = unsafe { builder.build_in_bounds_gep(array_data_ptr, &[offset_bytes], "elem") };
builder.build_load(elem_ptr, "List.get")
builder.build_load(elem_ptr, "List.get")
}
_ => {
unreachable!("Invalid List layout for List.get: {:?}", list_layout);
}
}
}
Symbol::LIST_SET /* TODO clone first for LIST_SET! */ | Symbol::LIST_SET_IN_PLACE => {
let builder = env.builder;
debug_assert!(args.len() == 3);
let wrapper_struct = args[0].into_struct_value();
let elem_index = args[1].into_int_value();
let elem = args[2];
let wrapper_struct = args[0].0.into_struct_value();
let elem_index = args[1].0.into_int_value();
let (elem, elem_layout) = args[2];
// Slot 1 in the wrapper struct is the length
let _list_len = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "unwrapped_list_len").unwrap().into_int_value();
@ -824,7 +1044,7 @@ fn call_with_args<'a, 'ctx, 'env>(
// Slot 0 in the wrapper struct is the pointer to the array data
let array_data_ptr = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "unwrapped_list_ptr").unwrap().into_pointer_value();
let elem_bytes = 8; // TODO Look this size up instead of hardcoding it!
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let elem_size = env.context.i64_type().const_int(elem_bytes, false);
// Calculate the offset at runtime by multiplying the index by the size of an element.
@ -845,7 +1065,13 @@ fn call_with_args<'a, 'ctx, 'env>(
.get_function(symbol.ident_string(&env.interns))
.unwrap_or_else(|| panic!("Unrecognized function: {:?}", symbol));
let call = env.builder.build_call(fn_val, args, "tmp");
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for (arg, _layout) in args.iter() {
arg_vals.push(*arg);
}
let call = env.builder.build_call(fn_val, arg_vals.into_bump_slice(), "call");
call.try_as_basic_value()
.left()

View file

@ -2,7 +2,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::types::BasicTypeEnum::{self, *};
use inkwell::types::{ArrayType, BasicType, FunctionType, PointerType, StructType};
use inkwell::types::{ArrayType, BasicType, FunctionType, IntType, PointerType, StructType};
use inkwell::AddressSpace;
use roc_mono::layout::Layout;
@ -38,17 +38,20 @@ pub fn basic_type_from_layout<'ctx>(
arena: &Bump,
context: &'ctx Context,
layout: &Layout<'_>,
ptr_bytes: u32,
) -> BasicTypeEnum<'ctx> {
use roc_mono::layout::Builtin::*;
use roc_mono::layout::Layout::*;
match layout {
FunctionPointer(args, ret_layout) => {
let ret_type = basic_type_from_layout(arena, context, &ret_layout);
let ret_type = basic_type_from_layout(arena, context, &ret_layout, ptr_bytes);
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
for arg_layout in args.iter() {
arg_basic_types.push(basic_type_from_layout(arena, context, arg_layout));
arg_basic_types.push(basic_type_from_layout(
arena, context, arg_layout, ptr_bytes,
));
}
let fn_type = get_fn_type(&ret_type, arg_basic_types.into_bump_slice());
@ -61,24 +64,50 @@ pub fn basic_type_from_layout<'ctx>(
let mut field_types = Vec::with_capacity_in(sorted_fields.len(), arena);
for (_, field_layout) in sorted_fields.iter() {
field_types.push(basic_type_from_layout(arena, context, field_layout));
field_types.push(basic_type_from_layout(
arena,
context,
field_layout,
ptr_bytes,
));
}
context
.struct_type(field_types.into_bump_slice(), false)
.as_basic_type_enum()
}
Tag(_fields) => {
panic!("TODO layout_to_basic_type for Tag");
Union(tags) if tags.len() == 1 => {
let layouts = tags.iter().next().unwrap();
// Determine types
let mut field_types = Vec::with_capacity_in(layouts.len(), arena);
for layout in layouts.iter() {
field_types.push(basic_type_from_layout(arena, context, layout, ptr_bytes));
}
context
.struct_type(field_types.into_bump_slice(), false)
.as_basic_type_enum()
}
Pointer(_layout) => {
panic!("TODO layout_to_basic_type for Pointer");
Union(_) => {
// TODO make this dynamic
let ptr_size = std::mem::size_of::<i64>();
let union_size = layout.stack_size(ptr_size as u32);
let array_type = context
.i8_type()
.array_type(union_size)
.as_basic_type_enum();
context.struct_type(&[array_type], false).into()
}
Builtin(builtin) => match builtin {
Int64 => context.i64_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(),
Bool(_, _) => context.bool_type().as_basic_type_enum(),
Byte(_) => context.i8_type().as_basic_type_enum(),
Bool => context.bool_type().as_basic_type_enum(),
Byte => context.i8_type().as_basic_type_enum(),
Str | EmptyStr => context
.i8_type()
.ptr_type(AddressSpace::Generic)
@ -86,17 +115,17 @@ pub fn basic_type_from_layout<'ctx>(
Map(_, _) | EmptyMap => panic!("TODO layout_to_basic_type for Builtin::Map"),
Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"),
List(elem_layout) => {
let ptr_type = basic_type_from_layout(arena, context, elem_layout)
let ptr_type = basic_type_from_layout(arena, context, elem_layout, ptr_bytes)
.ptr_type(AddressSpace::Generic);
collection_wrapper(context, ptr_type).into()
collection_wrapper(context, ptr_type, ptr_bytes).into()
}
EmptyList => {
let array_type =
get_array_type(&context.opaque_struct_type("empty_list_elem").into(), 0);
let ptr_type = array_type.ptr_type(AddressSpace::Generic);
collection_wrapper(context, ptr_type).into()
collection_wrapper(context, ptr_type, ptr_bytes).into()
}
},
}
@ -106,9 +135,24 @@ pub fn basic_type_from_layout<'ctx>(
pub fn collection_wrapper<'ctx>(
ctx: &'ctx Context,
ptr_type: PointerType<'ctx>,
ptr_bytes: u32,
) -> StructType<'ctx> {
let ptr_type_enum = BasicTypeEnum::PointerType(ptr_type);
let u32_type = BasicTypeEnum::IntType(ctx.i32_type());
let len_type = BasicTypeEnum::IntType(ptr_int(ctx, ptr_bytes));
ctx.struct_type(&[ptr_type_enum, u32_type, u32_type], false)
ctx.struct_type(&[ptr_type_enum, len_type], false)
}
pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {
match ptr_bytes {
1 => ctx.i8_type(),
2 => ctx.i16_type(),
4 => ctx.i32_type(),
8 => ctx.i64_type(),
16 => ctx.i128_type(),
_ => panic!(
"Invalid target: Roc does't support compiling to {}-bit systems.",
ptr_bytes * 8
),
}
}

View file

@ -77,6 +77,7 @@ mod test_gen {
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mono_expr = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE);
// Put this module's ident_ids back in the interns
env.interns.all_ident_ids.insert(home, ident_ids);
@ -147,7 +148,7 @@ mod test_gen {
builder.finalize();
}
module.define_function(main_fn, &mut ctx).expect("declare main");
module.define_function(main_fn, &mut ctx).expect("crane declare main");
module.clear_context(&mut ctx);
// Perform linking
@ -200,16 +201,16 @@ mod test_gen {
// Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let main_fn_type = basic_type_from_layout(&arena, &context, &layout)
.fn_type(&[], false);
let main_fn_name = "$Test.main";
let execution_engine =
module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
let ptr_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes)
.fn_type(&[], false);
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
@ -218,7 +219,7 @@ mod test_gen {
context: &context,
interns,
module: arena.alloc(module),
pointer_bytes
ptr_bytes
};
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
@ -226,6 +227,8 @@ mod test_gen {
// Populate Procs and get the low-level Expr from the canonical Expr
let main_body = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE);
dbg!(&main_body);
// Put this module's ident_ids back in the interns, so we can use them in Env.
env.interns.all_ident_ids.insert(home, ident_ids);
@ -335,16 +338,16 @@ mod test_gen {
// Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let main_fn_type = basic_type_from_layout(&arena, &context, &layout)
.fn_type(&[], false);
let main_fn_name = "$Test.main";
let execution_engine =
module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
let ptr_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes)
.fn_type(&[], false);
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
@ -353,7 +356,7 @@ mod test_gen {
context: &context,
interns,
module: arena.alloc(module),
pointer_bytes
ptr_bytes
};
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
@ -550,7 +553,26 @@ mod test_gen {
}
#[test]
fn get_shared_int_list() {
fn set_shared_int_list() {
assert_crane_evals_to!(
indoc!(
r#"
shared = [ 2, 4 ]
# This should not mutate the original
x = List.set shared 1 77
List.getUnsafe shared 1
"#
),
4,
i64,
|x| x
);
}
#[test]
fn get_unique_int_list() {
assert_evals_to!(
indoc!(
r#"
@ -594,6 +616,23 @@ mod test_gen {
);
}
// doesn't work yet. The condition must be cast to an integer to use a jump table
// #[test]
// fn branch_third_float() {
// assert_evals_to!(
// indoc!(
// r#"
// when 10.0 is
// 1.0 -> 63
// 2 -> 48
// _ -> 112
// "#
// ),
// 112.0,
// f64
// );
// }
#[test]
fn branch_first_int() {
assert_evals_to!(
@ -624,6 +663,85 @@ mod test_gen {
);
}
#[test]
fn branch_third_int() {
assert_evals_to!(
indoc!(
r#"
when 10 is
1 -> 63
2 -> 48
_ -> 112
"#
),
112,
i64
);
}
#[test]
fn branch_store_variable() {
assert_evals_to!(
indoc!(
r#"
when 0 is
1 -> 12
a -> a
"#
),
0,
i64
);
}
#[test]
fn one_element_tag() {
assert_evals_to!(
indoc!(
r#"
x : [ Pair Int ]
x = Pair 2
0x3
"#
),
3,
i64
);
}
#[test]
fn when_one_element_tag() {
assert_evals_to!(
indoc!(
r#"
x : [ Pair Int Int ]
x = Pair 0x2 0x3
when x is
Pair l r -> l + r
"#
),
5,
i64
);
}
#[test]
fn twice_record_access() {
assert_evals_to!(
indoc!(
r#"
x = {a: 0x2, b: 0x3 }
x.a + x.b
"#
),
5,
i64
);
}
#[test]
fn gen_when_one_branch() {
assert_evals_to!(
@ -1065,6 +1183,136 @@ mod test_gen {
);
}
#[test]
fn applied_tag_nothing() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
x : Maybe Int
x = Nothing
0x1
"#
),
1,
i64
);
}
#[test]
fn applied_tag_just() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
y : Maybe Int
y = Just 0x4
0x1
"#
),
1,
i64
);
}
//
// #[test]
// fn applied_tag_just_unit() {
// assert_evals_to!(
// indoc!(
// r#"
// Fruit : [ Orange, Apple, Banana ]
// Maybe a : [ Just a, Nothing ]
//
// orange : Fruit
// orange = Orange
//
// y : Maybe Fruit
// y = Just orange
//
// 0x1
// "#
// ),
// 1,
// i64
// );
// }
#[test]
fn when_on_nothing() {
assert_evals_to!(
indoc!(
r#"
x : [ Nothing, Just Int ]
x = Nothing
when x is
Nothing -> 0x2
Just _ -> 0x1
"#
),
2,
i64
);
}
// #[test]
// fn when_on_just() {
// assert_evals_to!(
// indoc!(
// r#"
// x : [ Nothing, Just Int ]
// x = Just 41
//
// case x of
// Just v -> v + 0x1
// Nothing -> 0x1
// "#
// ),
// 42,
// i64
// );
// }
#[test]
fn when_on_result() {
assert_evals_to!(
indoc!(
r#"
x : Result Int Int
x = Err 41
when x is
Err v -> v + 1
Ok _ -> 1
"#
),
42,
i64
);
}
#[test]
fn when_on_these() {
assert_evals_to!(
indoc!(
r#"
x : [ This Int, These Int Int ]
x = These 0x3 0x2
when x is
These a b -> a + b
This v -> v
"#
),
5,
i64
);
}
#[test]
fn basic_record() {
assert_evals_to!(
@ -1174,5 +1422,17 @@ mod test_gen {
19,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z + rec.x
"#
),
34,
i64
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,5 @@
use crate::layout::{Builtin, Layout};
use crate::pattern::Ctor;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_can;
@ -7,6 +8,7 @@ use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
use std::hash::Hash;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Procs<'a> {
@ -97,13 +99,14 @@ pub struct Proc<'a> {
pub ret_layout: Layout<'a>,
}
struct Env<'a, 'i> {
pub struct Env<'a, 'i> {
pub arena: &'a Bump,
pub subs: &'a mut Subs,
pub home: ModuleId,
pub ident_ids: &'i mut IdentIds,
pub pointer_size: u32,
symbol_counter: usize,
pub jump_counter: &'a mut u64,
}
impl<'a, 'i> Env<'a, 'i> {
@ -181,8 +184,10 @@ pub enum Expr<'a> {
},
Tag {
tag_layout: Layout<'a>,
name: TagName,
arguments: &'a [Expr<'a>],
tag_name: TagName,
tag_id: u8,
union_size: u8,
arguments: &'a [(Expr<'a>, Layout<'a>)],
},
Struct(&'a [(Expr<'a>, Layout<'a>)]),
Access {
@ -191,12 +196,21 @@ pub enum Expr<'a> {
struct_layout: Layout<'a>,
record: &'a Expr<'a>,
},
AccessAtIndex {
index: u64,
field_layouts: &'a [Layout<'a>],
expr: &'a Expr<'a>,
is_unwrapped: bool,
},
Array {
elem_layout: Layout<'a>,
elems: &'a [Expr<'a>],
},
Label(u64, &'a Expr<'a>),
Jump(u64),
RuntimeError(&'a str),
}
@ -217,6 +231,7 @@ impl<'a> Expr<'a> {
ident_ids,
pointer_size,
symbol_counter: 0,
jump_counter: arena.alloc(0),
};
from_can(&mut env, can_expr, procs, None)
@ -359,6 +374,7 @@ fn pattern_to_when<'a>(
}
}
#[allow(clippy::cognitive_complexity)]
fn from_can<'a>(
env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr,
@ -412,7 +428,7 @@ fn from_can<'a>(
store_pattern(
env,
mono_pattern,
loc_expr.value,
&loc_expr.value,
def.expr_var,
procs,
&mut stored,
@ -503,8 +519,8 @@ fn from_can<'a>(
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool(_, _) => Symbol::INT_EQ_I1,
Builtin::Byte(_) => Symbol::INT_EQ_I8,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
@ -572,6 +588,10 @@ fn from_can<'a>(
// It might even be the anonymous result of a conditional:
//
// ((if x > 0 then \a -> a else \_ -> 0) 5)
//
// It could be named too:
//
// ((if x > 0 then foo else bar) 5)
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (_, loc_arg) in loc_args {
@ -654,28 +674,68 @@ fn from_can<'a>(
Tag {
variant_var,
name,
name: tag_name,
arguments: args,
..
} => {
let arena = env.arena;
let mut fields = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(env.subs, variant_var, &mut fields) {
Ok(()) | Err((_, Content::FlexVar(_))) => {}
Err(content) => panic!("invalid content in ext_var: {:?}", content),
}
fields.sort();
let tag_id = fields
.iter()
.position(|(key, _)| key == &tag_name)
.expect("tag must be in its own type");
match Layout::from_var(arena, variant_var, &env.subs, env.pointer_size) {
Ok(Layout::Builtin(Builtin::Bool(_smaller, larger))) => Expr::Bool(name == larger),
Ok(Layout::Builtin(Builtin::Byte(tags))) => match tags.get(&name) {
Some(v) => Expr::Byte(*v),
None => panic!("Tag name is not part of the type"),
},
Ok(Layout::Builtin(Builtin::Bool)) => Expr::Bool(tag_id != 0),
Ok(Layout::Builtin(Builtin::Byte)) => Expr::Byte(tag_id as u8),
Ok(layout) => {
let mut arguments = Vec::with_capacity_in(args.len(), arena);
for (_, arg) in args {
arguments.push(from_can(env, arg.value, procs, None));
for (arg_var, arg) in args {
let arg_layout =
Layout::from_var(env.arena, arg_var, env.subs, env.pointer_size)
.expect("invalid ret_layout");
arguments.push((from_can(env, arg.value, procs, None), arg_layout));
}
let mut tags = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(
env.subs,
variant_var,
&mut tags,
) {
Ok(()) => {
tags.sort();
}
other => panic!("invalid value in ext_var {:?}", other),
}
let mut opt_tag_id = None;
for (index, (name, _)) in tags.iter().enumerate() {
if name == &tag_name {
opt_tag_id = Some(index as u8);
break;
}
}
let union_size = tags.len() as u8;
let tag_id = opt_tag_id.expect("Tag must be in its own type");
Expr::Tag {
tag_layout: layout,
name,
tag_name,
tag_id,
union_size,
arguments: arguments.into_bump_slice(),
}
}
@ -760,7 +820,7 @@ fn from_can<'a>(
fn store_pattern<'a>(
env: &mut Env<'a, '_>,
can_pat: Pattern,
can_expr: roc_can::expr::Expr,
can_expr: &roc_can::expr::Expr,
var: Variable,
procs: &mut Procs<'a>,
stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
@ -788,13 +848,15 @@ fn store_pattern<'a>(
// identity 5
//
match can_pat {
Identifier(symbol) => stored.push((symbol, layout, from_can(env, can_expr, procs, None))),
Identifier(symbol) => {
stored.push((symbol, layout, from_can(env, can_expr.clone(), procs, None)))
}
Underscore => {
// Since _ is never read, it's safe to reassign it.
stored.push((
Symbol::UNDERSCORE,
layout,
from_can(env, can_expr, procs, None),
from_can(env, can_expr.clone(), procs, None),
))
}
_ => {
@ -803,6 +865,96 @@ fn store_pattern<'a>(
}
}
fn store_pattern2<'a>(
env: &mut Env<'a, '_>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
_index: usize,
layout: Layout<'a>,
stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
) -> Result<(), String> {
use Pattern::*;
match can_pat {
Identifier(symbol) => {
if true {
// stored.push((*symbol, layout, Expr::Load(outer_symbol)))
// let load = Expr::AccessAtIndex {
// index: index as u64,
// field_layouts: env.arena.alloc([
// Layout::Builtin(Builtin::Int64),
// Layout::Builtin(Builtin::Int64),
// ]),
// expr: env.arena.alloc(Expr::Load(outer_symbol)),
// };
let load = Expr::Load(outer_symbol);
stored.push((*symbol, layout, load))
} else {
todo!()
}
}
Underscore => {
// Since _ is never read, it's safe to reassign it.
stored.push((Symbol::UNDERSCORE, layout, Expr::Load(outer_symbol)))
}
IntLiteral(_) | FloatLiteral(_) | EnumLiteral { .. } | BitLiteral(_) => {}
AppliedTag {
union, arguments, ..
} => {
let is_unwrapped = union.alternatives.len() == 1;
let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena);
if !is_unwrapped {
// add an element for the tag discriminant
arg_layouts.push(Layout::Builtin(Builtin::Int64));
}
for (_, layout) in arguments {
arg_layouts.push(layout.clone());
}
for (index, (argument, arg_layout)) in arguments.iter().enumerate() {
let load = Expr::AccessAtIndex {
is_unwrapped,
index: (!is_unwrapped as usize + index) as u64,
field_layouts: arg_layouts.clone().into_bump_slice(),
expr: env.arena.alloc(Expr::Load(outer_symbol)),
};
match argument {
Identifier(symbol) => {
// store immediately in the given symbol
stored.push((*symbol, arg_layout.clone(), load));
}
Underscore => {
// ignore
}
_ => {
// store the field in a symbol, and continue matching on it
let symbol = env.fresh_symbol();
stored.push((symbol, layout.clone(), load));
store_pattern2(env, argument, symbol, 0, arg_layout.clone(), stored)?;
}
}
}
}
Shadowed(region, ident) => {
return Err(format!(
"The pattern at {:?} shadows variable {:?}",
region, ident
));
}
_ => {
panic!("TODO store_pattern for {:?}", can_pat);
}
}
Ok(())
}
fn from_can_when<'a>(
env: &mut Env<'a, '_>,
cond_var: Variable,
@ -814,8 +966,6 @@ fn from_can_when<'a>(
)>,
procs: &mut Procs<'a>,
) -> Expr<'a> {
use Pattern::*;
match branches.len() {
0 => {
// A when-expression with no branches is a runtime error.
@ -830,241 +980,83 @@ fn from_can_when<'a>(
let (loc_when_pattern, loc_branch) = branches.into_iter().next().unwrap();
let mono_pattern = from_can_pattern(env, &loc_when_pattern.value);
store_pattern(
env,
mono_pattern,
loc_cond.value,
cond_var,
procs,
&mut stored,
);
let ret = from_can(env, loc_branch.value, procs, None);
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond_symbol = env.fresh_symbol();
let cond = from_can(env, loc_cond.value, procs, None);
stored.push((cond_symbol, cond_layout.clone(), cond));
// NOTE this will still store shadowed names. I think that is fine because the branch
// will throw an error anyway.
let ret = match store_pattern2(
env,
&mono_pattern,
cond_symbol,
0,
cond_layout,
&mut stored,
) {
Ok(_) => from_can(env, loc_branch.value, procs, None),
Err(message) => Expr::RuntimeError(env.arena.alloc(message)),
};
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
}
2 => {
let loc_branches: std::vec::Vec<_> = branches
.iter()
.map(|(loc_branch, _)| {
Located::at(loc_branch.region, from_can_pattern(env, &loc_branch.value))
})
.collect();
match crate::pattern::check(Region::zero(), &loc_branches) {
Ok(_) => {}
Err(errors) => panic!("Errors in patterns: {:?}", errors),
}
// A when-expression with exactly 2 branches compiles to a Cond.
let arena = env.arena;
let mut iter = branches.into_iter();
let (can_loc_when_pat1, loc_then) = iter.next().unwrap();
let (can_loc_when_pat2, loc_else) = iter.next().unwrap();
let when_pat1 = from_can_pattern(env, &can_loc_when_pat1.value);
let when_pat2 = from_can_pattern(env, &can_loc_when_pat2.value);
let cond_layout = Layout::Builtin(Builtin::Bool(
TagName::Global("False".into()),
TagName::Global("True".into()),
));
match (&when_pat1, &when_pat2) {
(IntLiteral(int), Underscore) => {
let cond_lhs = from_can(env, loc_cond.value, procs, None);
let cond_rhs = Expr::Int(*int);
let cond = arena.alloc(Expr::CallByName(
Symbol::INT_EQ_I64,
arena.alloc([
(cond_lhs, Layout::Builtin(Builtin::Int64)),
(cond_rhs, Layout::Builtin(Builtin::Int64)),
]),
));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| {
panic!("TODO turn this into a RuntimeError {:?}", err)
});
Expr::Cond {
cond_layout,
cond,
pass,
fail,
ret_layout,
}
}
(FloatLiteral(float), Underscore) => {
let cond_lhs = from_can(env, loc_cond.value, procs, None);
let cond_rhs = Expr::Float(*float);
let cond = arena.alloc(Expr::CallByName(
Symbol::FLOAT_EQ,
arena.alloc([
(cond_lhs, Layout::Builtin(Builtin::Float64)),
(cond_rhs, Layout::Builtin(Builtin::Float64)),
]),
));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| {
panic!("TODO turn this into a RuntimeError {:?}", err)
});
Expr::Cond {
cond_layout,
cond,
pass,
fail,
ret_layout,
}
}
_ => {
panic!("TODO handle more conds");
}
}
}
_ => {
let loc_branches: std::vec::Vec<_> = branches
.iter()
.map(|(loc_branch, _)| {
Located::at(loc_branch.region, from_can_pattern(env, &loc_branch.value))
})
.collect();
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond = from_can(env, loc_cond.value, procs, None);
let cond_symbol = env.fresh_symbol();
let mut loc_branches = std::vec::Vec::new();
let mut opt_branches = std::vec::Vec::new();
for (loc_pattern, loc_expr) in branches {
let mono_pattern = from_can_pattern(env, &loc_pattern.value);
loc_branches.push(Located::at(loc_pattern.region, mono_pattern.clone()));
let mut stores = Vec::with_capacity_in(1, env.arena);
let mono_expr = match store_pattern2(
env,
&mono_pattern,
cond_symbol,
0,
cond_layout.clone(),
&mut stores,
) {
Ok(_) => Expr::Store(
stores.into_bump_slice(),
env.arena.alloc(from_can(env, loc_expr.value, procs, None)),
),
Err(message) => Expr::RuntimeError(env.arena.alloc(message)),
};
opt_branches.push((mono_pattern, mono_expr));
}
match crate::pattern::check(Region::zero(), &loc_branches) {
Ok(_) => {}
Err(errors) => panic!("Errors in patterns: {:?}", errors),
}
// This is a when-expression with 3+ branches.
let arena = env.arena;
let cond = from_can(env, loc_cond.value, procs, None);
let subs = &env.subs;
let layout = Layout::from_var(arena, cond_var, subs, env.pointer_size)
.unwrap_or_else(|_| panic!("TODO generate a runtime error in from_can_when here!"));
let ret_layout = Layout::from_var(env.arena, expr_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
// We can Switch on integers and tags, because they both have
// representations that work as integer values.
//
// TODO we can also Switch on record fields if we're pattern matching
// on a record field that's also Switchable.
//
// TODO we can also convert floats to integer representations.
let is_switchable = match layout {
Layout::Builtin(Builtin::Int64) => true,
Layout::Builtin(Builtin::Bool(_, _)) => true,
Layout::Builtin(Builtin::Byte(_)) => true,
_ => false,
};
let branching = crate::decision_tree::optimize_when(
env,
cond_symbol,
cond_layout.clone(),
ret_layout,
opt_branches,
);
// If the condition is an Int or Float, we can potentially use
// a Switch for more efficiency.
if is_switchable {
// These are integer literals or underscore patterns,
// so they're eligible for user in a jump table.
let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena);
let mut opt_default_branch = None;
let stores = env.arena.alloc([(cond_symbol, cond_layout, cond)]);
let mut is_last = true;
for (loc_when_pat, loc_expr) in branches.into_iter().rev() {
let mono_expr = from_can(env, loc_expr.value, procs, None);
let when_pat = from_can_pattern(env, &loc_when_pat.value);
if is_last {
opt_default_branch = match &when_pat {
Identifier(symbol) => {
// TODO does this evaluate `cond` twice?
Some(arena.alloc(Expr::Store(
arena.alloc([(*symbol, layout.clone(), cond.clone())]),
arena.alloc(mono_expr.clone()),
)))
}
Shadowed(_region, _ident) => {
panic!("TODO make runtime exception out of the branch");
}
_ => Some(arena.alloc(mono_expr.clone())),
};
is_last = false;
}
match &when_pat {
IntLiteral(int) => {
// Switch only compares the condition to the
// alternatives based on their bit patterns,
// so casting from i64 to u64 makes no difference here.
jumpable_branches.push((*int as u64, mono_expr));
}
BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)),
EnumLiteral { tag_id, .. } => {
jumpable_branches.push((*tag_id as u64, mono_expr))
}
Identifier(_) => {
// store is handled above
}
Underscore => {}
Shadowed(_, _) => {
panic!("TODO runtime error for shadowing in a pattern");
}
UnsupportedPattern(_region) => unreachable!("When accepts all patterns"),
AppliedTag { .. }
| StrLiteral(_)
| RecordDestructure(_, _)
| FloatLiteral(_) => {
// The type checker should have converted these mismatches into RuntimeErrors already!
if cfg!(debug_assertions) {
panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", when_pat);
} else {
unreachable!();
}
}
}
}
// If the default branch was never set, that means
// our canonical Expr didn't have one. An earlier
// step in the compilation process should have
// ruled this out!
debug_assert!(opt_default_branch.is_some());
let default_branch = opt_default_branch.unwrap();
let cond_layout = Layout::from_var(arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| {
panic!("TODO turn cond_layout into a RuntimeError {:?}", err)
});
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| {
panic!("TODO turn ret_layout into a RuntimeError {:?}", err)
});
Expr::Switch {
cond: arena.alloc(cond),
branches: jumpable_branches.into_bump_slice(),
default_branch,
ret_layout,
cond_layout,
}
} else {
// /// More than two conditional branches, e.g. a 3-way when-expression
// Expr::Branches {
// /// The left-hand side of the conditional. We compile this to LLVM once,
// /// then reuse it to test against each different compiled cond_rhs value.
// cond_lhs: &'a Expr<'a>,
// /// ( cond_rhs, pass, fail )
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
// ret_var: Variable,
// },
panic!(
"TODO support when-expressions of 3+ branches whose conditions aren't integers."
);
}
Expr::Store(stores, env.arena.alloc(branching))
}
}
}
@ -1203,25 +1195,28 @@ fn specialize_proc_body<'a>(
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Pattern<'a> {
Identifier(Symbol),
AppliedTag {
tag_name: TagName,
arguments: Vec<'a, Pattern<'a>>,
layout: Layout<'a>,
union: crate::pattern::Union,
},
Underscore,
IntLiteral(i64),
FloatLiteral(u64),
BitLiteral(bool),
EnumLiteral {
tag_id: u8,
enum_size: u8,
},
IntLiteral(i64),
FloatLiteral(f64),
StrLiteral(Box<str>),
RecordDestructure(Vec<'a, RecordDestruct<'a>>, Layout<'a>),
Underscore,
AppliedTag {
tag_name: TagName,
tag_id: u8,
arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>,
layout: Layout<'a>,
union: crate::pattern::Union,
},
// Runtime Exceptions
Shadowed(Region, Located<Ident>),
@ -1229,7 +1224,7 @@ pub enum Pattern<'a> {
UnsupportedPattern(Region),
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RecordDestruct<'a> {
pub label: Lowercase,
pub symbol: Symbol,
@ -1245,14 +1240,14 @@ fn from_can_pattern<'a>(
Underscore => Pattern::Underscore,
Identifier(symbol) => Pattern::Identifier(*symbol),
IntLiteral(v) => Pattern::IntLiteral(*v),
FloatLiteral(v) => Pattern::FloatLiteral(*v),
FloatLiteral(v) => Pattern::FloatLiteral(f64::to_bits(*v)),
StrLiteral(v) => Pattern::StrLiteral(v.clone()),
Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()),
UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region),
NumLiteral(var, num) => match to_int_or_float(env.subs, *var) {
IntOrFloat::IntType => Pattern::IntLiteral(*num),
IntOrFloat::FloatType => Pattern::FloatLiteral(*num as f64),
IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64),
},
AppliedTag {
@ -1260,54 +1255,70 @@ fn from_can_pattern<'a>(
tag_name,
arguments,
..
} => match Layout::from_var(env.arena, *whole_var, env.subs, env.pointer_size) {
Ok(Layout::Builtin(Builtin::Bool(_bottom, top))) => {
Pattern::BitLiteral(tag_name == &top)
} => {
let mut fields = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(env.subs, *whole_var, &mut fields) {
Ok(()) | Err((_, Content::FlexVar(_))) => {}
Err(content) => panic!("invalid content in ext_var: {:?}", content),
}
Ok(Layout::Builtin(Builtin::Byte(conversion))) => match conversion.get(&tag_name) {
Some(index) => Pattern::EnumLiteral {
tag_id: *index,
enum_size: conversion.len() as u8,
fields.sort();
let tag_id = fields
.iter()
.position(|(key, _)| key == tag_name)
.expect("tag must be in its own type");
let enum_size = fields.len();
let mut ctors = std::vec::Vec::with_capacity(fields.len());
for (tag_name, args) in &fields {
ctors.push(Ctor {
name: tag_name.clone(),
arity: args.len(),
})
}
let union = crate::pattern::Union {
alternatives: ctors,
};
let fields_map: MutMap<_, _> = fields.into_iter().collect();
match crate::layout::layout_from_tag_union(
env.arena,
&fields_map,
env.subs,
env.pointer_size,
) {
Ok(Layout::Builtin(Builtin::Bool)) => Pattern::BitLiteral(tag_id != 0),
Ok(Layout::Builtin(Builtin::Byte)) => Pattern::EnumLiteral {
tag_id: tag_id as u8,
enum_size: enum_size as u8,
},
None => unreachable!("Tag must be in its own type"),
},
Ok(layout) => {
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
for (_, loc_pat) in arguments {
mono_args.push(from_can_pattern(env, &loc_pat.value));
}
Ok(layout) => {
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
for (pat_var, loc_pat) in arguments {
let layout =
Layout::from_var(env.arena, *pat_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| {
panic!("TODO turn pat_var into a RuntimeError {:?}", err)
});
let mut fields = std::vec::Vec::new();
let union = match roc_types::pretty_print::chase_ext_tag_union(
env.subs,
*whole_var,
&mut fields,
) {
Ok(()) | Err((_, Content::FlexVar(_))) => {
let mut ctors = std::vec::Vec::with_capacity(fields.len());
for (tag_name, args) in fields {
ctors.push(crate::pattern::Ctor {
name: tag_name.clone(),
arity: args.len(),
})
}
crate::pattern::Union {
alternatives: ctors,
}
mono_args.push((from_can_pattern(env, &loc_pat.value), layout));
}
Err(content) => panic!("invalid content in ext_var: {:?}", content),
};
Pattern::AppliedTag {
tag_name: tag_name.clone(),
arguments: mono_args,
union,
layout,
Pattern::AppliedTag {
tag_name: tag_name.clone(),
tag_id: tag_id as u8,
arguments: mono_args,
union,
layout,
}
}
Err(()) => panic!("Invalid layout"),
}
Err(()) => panic!("Invalid layout"),
},
}
RecordDestructure {
whole_var,

View file

@ -6,22 +6,21 @@ use roc_module::symbol::Symbol;
use roc_types::subs::{Content, FlatType, Subs, Variable};
/// Types for code gen must be monomorphic. No type variables allowed!
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Layout<'a> {
Builtin(Builtin<'a>),
Struct(&'a [(Lowercase, Layout<'a>)]),
Tag(&'a [Layout<'a>]),
Pointer(&'a Layout<'a>),
Union(&'a [&'a [Layout<'a>]]),
/// A function. The types of its arguments, then the type of its return value.
FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Builtin<'a> {
Int64,
Float64,
Bool(TagName, TagName),
Byte(MutMap<TagName, u8>),
Bool,
Byte,
Str,
Map(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>),
@ -79,6 +78,24 @@ impl<'a> Layout<'a> {
}
}
pub fn safe_to_memcpy(&self) -> bool {
use Layout::*;
match self {
Builtin(builtin) => builtin.safe_to_memcpy(),
Struct(fields) => fields
.iter()
.all(|(_, field_layout)| field_layout.safe_to_memcpy()),
Union(tags) => tags
.iter()
.all(|tag_layout| tag_layout.iter().all(|field| field.safe_to_memcpy())),
FunctionPointer(_, _) => {
// Function pointers are immutable and can always be safely copied
true
}
}
}
pub fn stack_size(&self, pointer_size: u32) -> u32 {
use Layout::*;
@ -93,17 +110,17 @@ impl<'a> Layout<'a> {
sum
}
Tag(fields) => {
// the symbol is a 64-bit value, so 8 bytes
let mut sum = 8;
for field_layout in *fields {
sum += field_layout.stack_size(pointer_size);
}
sum
}
Pointer(_) | FunctionPointer(_, _) => pointer_size,
Union(fields) => fields
.iter()
.map(|tag_layout| {
tag_layout
.iter()
.map(|field| field.stack_size(pointer_size))
.sum()
})
.max()
.unwrap_or_default(),
FunctionPointer(_, _) => pointer_size,
}
}
}
@ -131,17 +148,25 @@ impl<'a> Builtin<'a> {
match self {
Int64 => Builtin::I64_SIZE,
Float64 => Builtin::F64_SIZE,
Bool(_, _) => Builtin::BOOL_SIZE,
Byte(_) => Builtin::BYTE_SIZE,
Bool => Builtin::BOOL_SIZE,
Byte => Builtin::BYTE_SIZE,
Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size,
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
List(_) | EmptyList => Builtin::LIST_WORDS * pointer_size,
}
}
pub fn safe_to_memcpy(&self) -> bool {
use Builtin::*;
match self {
Int64 | Float64 | Bool | Byte | EmptyStr | EmptyMap | EmptyList | EmptySet => true,
Str | Map(_, _) | Set(_) | List(_) => false,
}
}
}
#[allow(clippy::cognitive_complexity)]
fn layout_from_flat_type<'a>(
arena: &'a Bump,
flat_type: FlatType,
@ -274,81 +299,7 @@ fn layout_from_flat_type<'a>(
TagUnion(tags, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
match tags.len() {
0 => {
panic!("TODO gracefully handle trying to instantiate Never");
}
// We can only unwrap a wrapper if it never becomes part of a bigger union
// therefore, the ext_var must be the literal empty tag union
1 => {
// This is a wrapper. Unwrap it!
let (tag, args) = tags.into_iter().next().unwrap();
match tag {
TagName::Private(Symbol::NUM_AT_NUM) => {
debug_assert!(args.len() == 1);
let var = args.into_iter().next().unwrap();
unwrap_num_tag(subs, var)
}
TagName::Private(symbol) => {
panic!("TODO emit wrapped private tag for {:?} {:?}", symbol, args);
}
TagName::Global(ident) => {
panic!("TODO emit wrapped global tag for {:?} {:?}", ident, args);
}
}
}
_ => {
// Check if we can turn this tag union into an enum
// The arguments of all tags must have size 0.
// That is trivially the case when there are no arguments
//
// [ Orange, Apple, Banana ]
//
// But when one-tag tag unions are optimized away, we can also use an enum for
//
// [ Foo [ Unit ], Bar [ Unit ] ]
let arguments_have_size_0 = || {
tags.iter().all(|(_, args)| {
args.iter().all(|var| {
Layout::from_var(arena, *var, subs, pointer_size)
.map(|v| v.stack_size(pointer_size))
== Ok(0)
})
})
};
// up to 256 enum keys can be stored in a byte
if tags.len() <= std::u8::MAX as usize + 1 && arguments_have_size_0() {
if tags.len() <= 2 {
// Up to 2 enum tags can be stored (in theory) in one bit
let mut it = tags.keys();
let a: TagName = it.next().unwrap().clone();
let b: TagName = it.next().unwrap().clone();
if a < b {
Ok(Layout::Builtin(Builtin::Bool(a, b)))
} else {
Ok(Layout::Builtin(Builtin::Bool(b, a)))
}
} else {
// up to 256 enum tags can be stored in a byte
let mut tag_to_u8 = MutMap::default();
for (counter, (name, _)) in tags.into_iter().enumerate() {
tag_to_u8.insert(name, counter as u8);
}
Ok(Layout::Builtin(Builtin::Byte(tag_to_u8)))
}
} else {
// panic!("TODO handle a tag union with mutliple tags: {:?}", tags);
Ok(Layout::Tag(&[]))
}
}
}
layout_from_tag_union(arena, &tags, subs, pointer_size)
}
RecursiveTagUnion(_, _, _) => {
panic!("TODO make Layout for non-empty Tag Union");
@ -364,6 +315,100 @@ fn layout_from_flat_type<'a>(
}
}
pub fn layout_from_tag_union<'a>(
arena: &'a Bump,
tags: &MutMap<TagName, std::vec::Vec<Variable>>,
subs: &Subs,
pointer_size: u32,
) -> Result<Layout<'a>, ()> {
match tags.len() {
0 => {
panic!("TODO gracefully handle trying to instantiate Never");
}
// We can only unwrap a wrapper if it never becomes part of a bigger union
// therefore, the ext_var must be the literal empty tag union
1 => {
// This is a wrapper. Unwrap it!
let (tag_name, arguments) = tags.iter().next().unwrap();
match &tag_name {
TagName::Private(Symbol::NUM_AT_NUM) => {
debug_assert!(arguments.len() == 1);
let var = arguments.iter().next().unwrap();
unwrap_num_tag(subs, *var)
}
TagName::Private(_) | TagName::Global(_) => {
let mut arg_layouts = Vec::with_capacity_in(arguments.len(), arena);
for arg in arguments {
arg_layouts.push(Layout::from_var(arena, *arg, subs, pointer_size)?);
}
let layouts = [arg_layouts.into_bump_slice()];
Ok(Layout::Union(arena.alloc(layouts)))
}
}
}
_ => {
// Check if we can turn this tag union into an enum
// The arguments of all tags must have size 0.
// That is trivially the case when there are no arguments
//
// [ Orange, Apple, Banana ]
//
// But when one-tag tag unions are optimized away, we can also use an enum for
//
// [ Foo [ Unit ], Bar [ Unit ] ]
let arguments_have_size_0 = || {
tags.iter().all(|(_, args)| {
args.iter().all(|var| {
Layout::from_var(arena, *var, subs, pointer_size)
.map(|v| v.stack_size(pointer_size))
== Ok(0)
})
})
};
// up to 256 enum keys can be stored in a byte
if tags.len() <= std::u8::MAX as usize + 1 && arguments_have_size_0() {
if tags.len() <= 2 {
Ok(Layout::Builtin(Builtin::Bool))
} else {
// up to 256 enum tags can be stored in a byte
Ok(Layout::Builtin(Builtin::Byte))
}
} else {
let add_discriminant = tags.len() != 1;
let mut layouts = Vec::with_capacity_in(tags.len(), arena);
for arguments in tags.values() {
// add a field for the discriminant if there is more than one tag in the union
let mut arg_layouts = if add_discriminant {
let discriminant = Layout::Builtin(Builtin::Int64);
let mut result = Vec::with_capacity_in(arguments.len() + 1, arena);
result.push(discriminant);
result
} else {
Vec::with_capacity_in(arguments.len(), arena)
};
for arg in arguments {
arg_layouts.push(Layout::from_var(arena, *arg, subs, pointer_size)?);
}
layouts.push(arg_layouts.into_bump_slice());
}
Ok(Layout::Union(arena.alloc(layouts)))
}
}
}
}
fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
// the ext_var is empty
let mut ext_fields = std::vec::Vec::new();

View file

@ -16,4 +16,6 @@ pub mod layout;
// Temporary, while we can build up test cases and optimize the exhaustiveness checking.
// For now, following this warning's advice will lead to nasty type inference errors.
#[allow(clippy::ptr_arg)]
pub mod decision_tree;
#[allow(clippy::ptr_arg)]
pub mod pattern;

View file

@ -4,14 +4,15 @@ use roc_region::all::{Located, Region};
use self::Pattern::*;
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Union {
pub alternatives: Vec<Ctor>,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ctor {
pub name: TagName,
// pub tag_id: u8,
pub arity: usize,
}
@ -27,7 +28,7 @@ pub enum Literal {
Int(i64),
Bit(bool),
Byte(u8),
Float(f64),
Float(u64),
Str(Box<str>),
}
@ -94,7 +95,7 @@ fn simplify<'a>(pattern: &crate::expr::Pattern<'a>) -> Pattern {
..
} => {
let simplified_args: std::vec::Vec<_> =
arguments.iter().map(|v| simplify(&v)).collect();
arguments.iter().map(|v| simplify(&v.0)).collect();
Ctor(union.clone(), tag_name.clone(), simplified_args)
}
}
@ -254,7 +255,6 @@ fn recover_ctor(
arity: usize,
mut patterns: Vec<Pattern>,
) -> Vec<Pattern> {
// TODO ensure that this behaves the same as haskell's splitAt
let mut rest = patterns.split_off(arity);
let args = patterns;

View file

@ -13,8 +13,6 @@ mod helpers;
mod test_mono {
use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut};
use bumpalo::Bump;
use roc_collections::all::MutMap;
use roc_module::ident::TagName::*;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::expr::Expr::{self, *};
use roc_mono::expr::Procs;
@ -164,7 +162,7 @@ mod test_mono {
Cond {
cond: &Expr::Bool(true),
cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))),
cond_layout: Builtin(Bool),
pass: &Expr::Str("bar"),
fail: &Expr::Str("foo"),
ret_layout: Builtin(Str),
@ -190,11 +188,11 @@ mod test_mono {
Cond {
cond: &Expr::Bool(true),
cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))),
cond_layout: Builtin(Bool),
pass: &Expr::Str("bar"),
fail: &Cond {
cond: &Expr::Bool(false),
cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))),
cond_layout: Builtin(Bool),
pass: &Expr::Str("foo"),
fail: &Expr::Str("baz"),
ret_layout: Builtin(Str),
@ -228,10 +226,7 @@ mod test_mono {
Builtin(Str),
Cond {
cond: &Expr::Bool(true),
cond_layout: Builtin(Bool(
Global("False".into()),
Global("True".into()),
)),
cond_layout: Builtin(Bool),
pass: &Expr::Str("bar"),
fail: &Expr::Str("foo"),
ret_layout: Builtin(Str),
@ -361,11 +356,7 @@ mod test_mono {
let home = test_home();
let var_x = interns.symbol(home, "x".into());
let stores = [(
var_x,
Layout::Builtin(Builtin::Bool(Global("False".into()), Global("True".into()))),
Bool(true),
)];
let stores = [(var_x, Layout::Builtin(Builtin::Bool), Bool(true))];
let load = Load(var_x);
@ -389,11 +380,7 @@ mod test_mono {
let home = test_home();
let var_x = interns.symbol(home, "x".into());
let stores = [(
var_x,
Layout::Builtin(Builtin::Bool(Global("No".into()), Global("Yes".into()))),
Bool(false),
)];
let stores = [(var_x, Layout::Builtin(Builtin::Bool), Bool(false))];
let load = Load(var_x);
@ -418,13 +405,8 @@ mod test_mono {
let home = test_home();
let var_x = interns.symbol(home, "x".into());
let mut fruits = MutMap::default();
fruits.insert(Global("Banana".into()), 0);
fruits.insert(Global("Orange".into()), 1);
fruits.insert(Global("Apple".into()), 2);
let stores = [(var_x, Layout::Builtin(Builtin::Byte(fruits)), Byte(1))];
// orange gets index (and therefore tag_id) 1
let stores = [(var_x, Layout::Builtin(Builtin::Byte), Byte(2))];
let load = Load(var_x);
@ -466,32 +448,31 @@ mod test_mono {
// #[test]
// fn when_on_result() {
// let arena = Bump::new();
//
// compiles_to_with_interns(
// compiles_to(
// r#"
// x = Ok 0x3
//
// when x is
// Err _ -> 0
// Ok n -> n + 1
// when 1 is
// 1 -> 12
// _ -> 34
// "#,
// |interns| {
// {
// use self::Builtin::*;
// use Layout::Builtin;
// let home = test_home();
// let var_x = interns.symbol(home, "x".into());
//
// let mut fruits = MutMap::default();
// let gen_symbol_3 = Interns::from_index(home, 3);
// let gen_symbol_4 = Interns::from_index(home, 4);
//
// fruits.insert(Global("Banana".into()), 0);
// fruits.insert(Global("Orange".into()), 1);
// fruits.insert(Global("Apple".into()), 2);
//
// let stores = [(var_x, Layout::Builtin(Builtin::Byte(fruits)), Byte(1))];
//
// let load = Load(var_x);
//
// Store(arena.alloc(stores), arena.alloc(load))
// CallByName(
// gen_symbol_3,
// &[(
// Struct(&[(
// CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]),
// Builtin(Int64),
// )]),
// Layout::Struct(&[("x".into(), Builtin(Int64))]),
// )],
// )
// },
// );
// )
// }
}

View file

@ -520,6 +520,11 @@ pub fn chase_ext_tag_union(
chase_ext_tag_union(subs, ext_var, fields)
}
Content::Structure(Apply(Symbol::ATTR_ATTR, arguments)) => {
debug_assert!(arguments.len() == 2);
chase_ext_tag_union(subs, arguments[1], fields)
}
Content::Alias(_, _, var) => chase_ext_tag_union(subs, var, fields),
content => Err((var, content)),
}