(llvm) Generate code for tag unions less than 64 bits in size correctly

Previously, we assumed that a union layout always lived on >= 1 64-bit
boundary when generating an LLVM type for it. For small tags unions,
like `[ Ok i8, Err ]` this need not be the case; indeed, a tag union
like that is actually only 2 bits - 1 bit for the "i8" data, and one bit
of the tag kind.

This led to a discrepancy between what the layout IR and generated LLVM
code would assume about the size of tag unions. In the case above, the
layout IR would assume the tag data is 2 bits wide, and the tag id is 1
bit into the data. But the LLVM code would generate a type that was 65
bits wide, the first 64 bits being for the "i8" data and the last 1 bit
being for the tag kind.

Usually, just running the LLVM-emitted code would not present a problem.
But it does present a problem when we use the layout IR to inspect the
result of LLVM-run code, in particular when we try to look up the tag
ID, as the repl does. This patch fixes that issue.

Note that this bug did not present itself in `test_gen` previously
because the data that most tests check against is stored in the front of
the representation.

Closes #2149
This commit is contained in:
ayazhafiz 2021-12-26 11:45:02 -06:00
parent f314abfed9
commit 5e5eb6dca8
4 changed files with 54 additions and 40 deletions

View file

@ -338,14 +338,9 @@ impl<'a> UnionLayout<'a> {
pub fn tag_id_builtin(&self) -> Builtin<'a> {
match self {
UnionLayout::NonRecursive(_tags) => {
// let union_size = tags.len();
// Self::tag_id_builtin_help(union_size)
// The quicksort-benchmarks version of Quicksort.roc segfaults when
// this number is not I64. There must be some dependence on that fact
// somewhere in the code, I have not found where that is yet...
Builtin::Int(IntWidth::U64)
UnionLayout::NonRecursive(tags) => {
let union_size = tags.len();
Self::tag_id_builtin_help(union_size)
}
UnionLayout::Recursive(tags) => {
let union_size = tags.len();
@ -445,6 +440,27 @@ impl<'a> UnionLayout<'a> {
None
};
self.data_size_and_alignment_help_match(id_data_layout, pointer_size)
}
/// Size of the data before the tag_id, if it exists.
/// Returns None if the tag_id is not stored as data in the layout.
pub fn data_size_without_tag_id(&self, pointer_size: u32) -> Option<u32> {
if !self.stores_tag_id_as_data(pointer_size) {
return None;
};
return Some(
self.data_size_and_alignment_help_match(None, pointer_size)
.0,
);
}
fn data_size_and_alignment_help_match(
&self,
id_data_layout: Option<Layout>,
pointer_size: u32,
) -> (u32, u32) {
match self {
Self::NonRecursive(tags) => {
Self::data_size_and_alignment_help(tags, id_data_layout, pointer_size)
@ -788,7 +804,7 @@ impl<'a, 'b> Env<'a, 'b> {
}
}
const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 {
pub const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 {
if alignment != 0 && width % alignment > 0 {
width + alignment - (width % alignment)
} else {
@ -934,23 +950,7 @@ impl<'a> Layout<'a> {
use UnionLayout::*;
match variant {
NonRecursive(fields) => {
let tag_id_builtin = variant.tag_id_builtin();
fields
.iter()
.map(|tag_layout| {
tag_layout
.iter()
.map(|field| field.stack_size(pointer_size))
.sum::<u32>()
})
.max()
.map(|w| round_up_to_alignment(w, tag_id_builtin.alignment_bytes(pointer_size)))
.unwrap_or_default()
// the size of the tag_id
+ tag_id_builtin.stack_size(pointer_size)
}
NonRecursive(_) => variant.data_size_and_alignment(pointer_size).0,
Recursive(_)
| NullableWrapped { .. }