mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
1085 lines
42 KiB
Rust
1085 lines
42 KiB
Rust
use bumpalo::collections::Vec;
|
|
use bumpalo::Bump;
|
|
use libloading::Library;
|
|
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
|
use roc_module::called_via::CalledVia;
|
|
use roc_module::ident::TagName;
|
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
|
use roc_mono::ir::ProcLayout;
|
|
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
|
|
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
|
|
use roc_region::all::{Located, Region};
|
|
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
|
|
|
|
struct Env<'a, 'env> {
|
|
arena: &'a Bump,
|
|
subs: &'env Subs,
|
|
ptr_bytes: u32,
|
|
interns: &'env Interns,
|
|
home: ModuleId,
|
|
}
|
|
|
|
pub enum ToAstProblem {
|
|
FunctionLayout,
|
|
}
|
|
|
|
/// JIT execute the given main function, and then wrap its results in an Expr
|
|
/// so we can display them to the user using the formatter.
|
|
///
|
|
/// We need the original types in order to properly render records and tag unions,
|
|
/// because at runtime those are structs - that is, unlabeled memory offsets.
|
|
/// By traversing the type signature while we're traversing the layout, once
|
|
/// we get to a struct or tag, we know what the labels are and can turn them
|
|
/// back into the appropriate user-facing literals.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub unsafe fn jit_to_ast<'a>(
|
|
arena: &'a Bump,
|
|
lib: Library,
|
|
main_fn_name: &str,
|
|
layout: ProcLayout<'a>,
|
|
content: &Content,
|
|
interns: &Interns,
|
|
home: ModuleId,
|
|
subs: &Subs,
|
|
ptr_bytes: u32,
|
|
) -> Result<Expr<'a>, ToAstProblem> {
|
|
let env = Env {
|
|
arena,
|
|
subs,
|
|
ptr_bytes,
|
|
interns,
|
|
home,
|
|
};
|
|
|
|
match layout {
|
|
ProcLayout {
|
|
arguments: [],
|
|
result,
|
|
} => {
|
|
// this is a thunk
|
|
jit_to_ast_help(&env, lib, main_fn_name, &result, content)
|
|
}
|
|
_ => Err(ToAstProblem::FunctionLayout),
|
|
}
|
|
}
|
|
|
|
fn jit_to_ast_help<'a>(
|
|
env: &Env<'a, '_>,
|
|
lib: Library,
|
|
main_fn_name: &str,
|
|
layout: &Layout<'a>,
|
|
content: &Content,
|
|
) -> Result<Expr<'a>, ToAstProblem> {
|
|
match layout {
|
|
Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| {
|
|
bool_to_ast(env, num, content)
|
|
})),
|
|
Layout::Builtin(Builtin::Int8) => {
|
|
Ok(
|
|
// NOTE: this is does not handle 8-bit numbers yet
|
|
run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)),
|
|
)
|
|
}
|
|
Layout::Builtin(Builtin::Usize) => Ok(run_jit_function!(lib, main_fn_name, usize, |num| {
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
})),
|
|
Layout::Builtin(Builtin::Int16) => {
|
|
Ok(run_jit_function!(lib, main_fn_name, i16, |num| num_to_ast(
|
|
env,
|
|
number_literal_to_ast(env.arena, num),
|
|
content
|
|
)))
|
|
}
|
|
Layout::Builtin(Builtin::Int32) => {
|
|
Ok(run_jit_function!(lib, main_fn_name, i32, |num| num_to_ast(
|
|
env,
|
|
number_literal_to_ast(env.arena, num),
|
|
content
|
|
)))
|
|
}
|
|
Layout::Builtin(Builtin::Int64) => {
|
|
Ok(run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
|
|
env,
|
|
number_literal_to_ast(env.arena, num),
|
|
content
|
|
)))
|
|
}
|
|
Layout::Builtin(Builtin::Int128) => {
|
|
Ok(run_jit_function!(
|
|
lib,
|
|
main_fn_name,
|
|
i128,
|
|
|num| num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
))
|
|
}
|
|
Layout::Builtin(Builtin::Float32) => {
|
|
Ok(run_jit_function!(lib, main_fn_name, f32, |num| num_to_ast(
|
|
env,
|
|
number_literal_to_ast(env.arena, num),
|
|
content
|
|
)))
|
|
}
|
|
Layout::Builtin(Builtin::Float64) => {
|
|
Ok(run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast(
|
|
env,
|
|
number_literal_to_ast(env.arena, num),
|
|
content
|
|
)))
|
|
}
|
|
Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => Ok(
|
|
run_jit_function!(lib, main_fn_name, &'static str, |string: &'static str| {
|
|
str_to_ast(env.arena, env.arena.alloc(string))
|
|
}),
|
|
),
|
|
Layout::Builtin(Builtin::EmptyList) => {
|
|
Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| {
|
|
Expr::List(Collection::empty())
|
|
}))
|
|
}
|
|
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!(
|
|
lib,
|
|
main_fn_name,
|
|
(*const u8, usize),
|
|
|(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) }
|
|
)),
|
|
Layout::Builtin(other) => {
|
|
todo!("add support for rendering builtin {:?} to the REPL", other)
|
|
}
|
|
Layout::Struct(field_layouts) => {
|
|
let ptr_to_ast = |ptr: *const u8| match content {
|
|
Content::Structure(FlatType::Record(fields, _)) => {
|
|
Ok(struct_to_ast(env, ptr, field_layouts, *fields))
|
|
}
|
|
Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast(
|
|
env,
|
|
ptr,
|
|
field_layouts,
|
|
RecordFields::empty(),
|
|
)),
|
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
|
debug_assert_eq!(tags.len(), 1);
|
|
|
|
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
|
|
|
Ok(single_tag_union_to_ast(
|
|
env,
|
|
ptr,
|
|
field_layouts,
|
|
tag_name,
|
|
payload_vars,
|
|
))
|
|
}
|
|
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => {
|
|
let tag_name = &env.subs[*tag_name];
|
|
|
|
Ok(single_tag_union_to_ast(
|
|
env,
|
|
ptr,
|
|
field_layouts,
|
|
tag_name,
|
|
&[],
|
|
))
|
|
}
|
|
Content::Structure(FlatType::Func(_, _, _)) => {
|
|
// a function with a struct as the closure environment
|
|
Err(ToAstProblem::FunctionLayout)
|
|
}
|
|
other => {
|
|
unreachable!(
|
|
"Something had a Struct layout, but instead of a Record or TagUnion type, it had: {:?}",
|
|
other
|
|
);
|
|
}
|
|
};
|
|
|
|
let fields = [Layout::Builtin(Builtin::Int64), *layout];
|
|
let layout = Layout::Struct(&fields);
|
|
|
|
let result_stack_size = layout.stack_size(env.ptr_bytes);
|
|
|
|
run_jit_function_dynamic_type!(
|
|
lib,
|
|
main_fn_name,
|
|
result_stack_size as usize,
|
|
|bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
|
|
)
|
|
}
|
|
Layout::Union(UnionLayout::NonRecursive(union_layouts)) => {
|
|
let union_layout = UnionLayout::NonRecursive(union_layouts);
|
|
|
|
match content {
|
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
|
debug_assert_eq!(union_layouts.len(), tags.len());
|
|
|
|
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = tags
|
|
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
|
|
.map(|(a, b)| (a.clone(), b.to_vec()))
|
|
.collect();
|
|
|
|
let tags_map: roc_collections::all::MutMap<_, _> =
|
|
tags_vec.iter().cloned().collect();
|
|
|
|
let union_variant =
|
|
union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes);
|
|
|
|
let size = layout.stack_size(env.ptr_bytes);
|
|
use roc_mono::layout::WrappedVariant::*;
|
|
match union_variant {
|
|
UnionVariant::Wrapped(variant) => {
|
|
match variant {
|
|
NonRecursive {
|
|
sorted_tag_layouts: tags_and_layouts,
|
|
} => {
|
|
Ok(run_jit_function_dynamic_type!(
|
|
lib,
|
|
main_fn_name,
|
|
size as usize,
|
|
|ptr: *const u8| {
|
|
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
|
|
let offset = tags_and_layouts
|
|
.iter()
|
|
.map(|(_, fields)| {
|
|
fields
|
|
.iter()
|
|
.map(|l| l.stack_size(env.ptr_bytes))
|
|
.sum()
|
|
})
|
|
.max()
|
|
.unwrap_or(0);
|
|
|
|
let tag_id = match union_layout.tag_id_builtin() {
|
|
Builtin::Bool => {
|
|
*(ptr.add(offset as usize) as *const i8) as i64
|
|
}
|
|
Builtin::Int8 => {
|
|
*(ptr.add(offset as usize) as *const i8) as i64
|
|
}
|
|
Builtin::Int16 => {
|
|
*(ptr.add(offset as usize) as *const i16) as i64
|
|
}
|
|
Builtin::Int64 => {
|
|
// used by non-recursive unions at the
|
|
// moment, remove if that is no longer the case
|
|
*(ptr.add(offset as usize) as *const i64) as i64
|
|
}
|
|
_ => unreachable!("invalid tag id layout"),
|
|
};
|
|
|
|
// use the tag ID as an index, to get its name and layout of any arguments
|
|
let (tag_name, arg_layouts) =
|
|
&tags_and_layouts[tag_id as usize];
|
|
|
|
let tag_expr = tag_name_to_expr(env, tag_name);
|
|
let loc_tag_expr =
|
|
&*env.arena.alloc(Located::at_zero(tag_expr));
|
|
|
|
let variables = &tags_map[tag_name];
|
|
|
|
debug_assert_eq!(arg_layouts.len(), variables.len());
|
|
|
|
// NOTE assumes the data bytes are the first bytes
|
|
let it =
|
|
variables.iter().copied().zip(arg_layouts.iter());
|
|
let output = sequence_of_expr(env, ptr, it);
|
|
let output = output.into_bump_slice();
|
|
|
|
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
|
|
}
|
|
))
|
|
}
|
|
Recursive {
|
|
sorted_tag_layouts: tags_and_layouts,
|
|
} => {
|
|
Ok(run_jit_function_dynamic_type!(
|
|
lib,
|
|
main_fn_name,
|
|
size as usize,
|
|
|ptr: *const u8| {
|
|
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
|
|
let tag_id = *(ptr as *const i64);
|
|
|
|
// use the tag ID as an index, to get its name and layout of any arguments
|
|
let (tag_name, arg_layouts) =
|
|
&tags_and_layouts[tag_id as usize];
|
|
|
|
let tag_expr = tag_name_to_expr(env, tag_name);
|
|
let loc_tag_expr =
|
|
&*env.arena.alloc(Located::at_zero(tag_expr));
|
|
|
|
let variables = &tags_map[tag_name];
|
|
|
|
// because the arg_layouts include the tag ID, it is one longer
|
|
debug_assert_eq!(
|
|
arg_layouts.len() - 1,
|
|
variables.len()
|
|
);
|
|
|
|
// skip forward to the start of the first element, ignoring the tag id
|
|
let ptr = ptr.offset(8);
|
|
|
|
let it =
|
|
variables.iter().copied().zip(&arg_layouts[1..]);
|
|
let output = sequence_of_expr(env, ptr, it);
|
|
let output = output.into_bump_slice();
|
|
|
|
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
|
|
}
|
|
))
|
|
}
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
_ => unreachable!("any other variant would have a different layout"),
|
|
}
|
|
}
|
|
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
|
|
todo!("print recursive tag unions in the REPL")
|
|
}
|
|
Content::Alias(_, _, actual) => {
|
|
let content = env.subs.get_content_without_compacting(*actual);
|
|
|
|
jit_to_ast_help(env, lib, main_fn_name, layout, content)
|
|
}
|
|
other => unreachable!("Weird content for Union layout: {:?}", other),
|
|
}
|
|
}
|
|
Layout::Union(UnionLayout::Recursive(_))
|
|
| Layout::Union(UnionLayout::NullableWrapped { .. })
|
|
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
|
|
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
|
|
| Layout::RecursivePointer => {
|
|
todo!("add support for rendering recursive tag unions in the REPL")
|
|
}
|
|
Layout::LambdaSet(lambda_set) => jit_to_ast_help(
|
|
env,
|
|
lib,
|
|
main_fn_name,
|
|
&lambda_set.runtime_representation(),
|
|
content,
|
|
),
|
|
}
|
|
}
|
|
|
|
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
|
|
match tag_name {
|
|
TagName::Global(_) => Expr::GlobalTag(
|
|
env.arena
|
|
.alloc_str(&tag_name.as_ident_str(env.interns, env.home)),
|
|
),
|
|
TagName::Private(_) => Expr::PrivateTag(
|
|
env.arena
|
|
.alloc_str(&tag_name.as_ident_str(env.interns, env.home)),
|
|
),
|
|
TagName::Closure(_) => unreachable!("User cannot type this"),
|
|
}
|
|
}
|
|
|
|
fn ptr_to_ast<'a>(
|
|
env: &Env<'a, '_>,
|
|
ptr: *const u8,
|
|
layout: &Layout<'a>,
|
|
content: &Content,
|
|
) -> Expr<'a> {
|
|
match layout {
|
|
Layout::Builtin(Builtin::Int128) => {
|
|
let num = unsafe { *(ptr as *const i128) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::Int64) => {
|
|
let num = unsafe { *(ptr as *const i64) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::Int32) => {
|
|
let num = unsafe { *(ptr as *const i32) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::Int16) => {
|
|
let num = unsafe { *(ptr as *const i16) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::Int8) => {
|
|
let num = unsafe { *(ptr as *const i8) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::Bool) => {
|
|
// TODO: bits are not as expected here.
|
|
// num is always false at the moment.
|
|
let num = unsafe { *(ptr as *const bool) };
|
|
|
|
bool_to_ast(env, num, content)
|
|
}
|
|
Layout::Builtin(Builtin::Usize) => {
|
|
let num = unsafe { *(ptr as *const usize) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::Float64) => {
|
|
let num = unsafe { *(ptr as *const f64) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::Float32) => {
|
|
let num = unsafe { *(ptr as *const f32) };
|
|
|
|
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
|
}
|
|
Layout::Builtin(Builtin::EmptyList) => Expr::List(Collection::empty()),
|
|
Layout::Builtin(Builtin::List(elem_layout)) => {
|
|
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
|
|
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
|
|
let ptr = unsafe { *(ptr as *const *const u8) };
|
|
|
|
list_to_ast(env, ptr, len, elem_layout, content)
|
|
}
|
|
Layout::Builtin(Builtin::EmptyStr) => Expr::Str(StrLiteral::PlainLine("")),
|
|
Layout::Builtin(Builtin::Str) => {
|
|
let arena_str = unsafe { *(ptr as *const &'static str) };
|
|
|
|
str_to_ast(env.arena, arena_str)
|
|
}
|
|
Layout::Struct(field_layouts) => match content {
|
|
Content::Structure(FlatType::Record(fields, _)) => {
|
|
struct_to_ast(env, ptr, field_layouts, *fields)
|
|
}
|
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
|
debug_assert_eq!(tags.len(), 1);
|
|
|
|
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
|
single_tag_union_to_ast(env, ptr, field_layouts, tag_name, payload_vars)
|
|
}
|
|
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => {
|
|
let tag_name = &env.subs[*tag_name];
|
|
single_tag_union_to_ast(env, ptr, field_layouts, tag_name, &[])
|
|
}
|
|
Content::Structure(FlatType::EmptyRecord) => {
|
|
struct_to_ast(env, ptr, &[], RecordFields::empty())
|
|
}
|
|
other => {
|
|
unreachable!(
|
|
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
|
other
|
|
);
|
|
}
|
|
},
|
|
other => {
|
|
todo!(
|
|
"TODO add support for rendering pointer to {:?} in the REPL",
|
|
other
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn list_to_ast<'a>(
|
|
env: &Env<'a, '_>,
|
|
ptr: *const u8,
|
|
len: usize,
|
|
elem_layout: &Layout<'a>,
|
|
content: &Content,
|
|
) -> Expr<'a> {
|
|
let elem_content = match content {
|
|
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => {
|
|
debug_assert_eq!(vars.len(), 1);
|
|
|
|
let elem_var_index = vars.into_iter().next().unwrap();
|
|
let elem_var = env.subs[elem_var_index];
|
|
|
|
env.subs.get_content_without_compacting(elem_var)
|
|
}
|
|
other => {
|
|
unreachable!(
|
|
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
|
other
|
|
);
|
|
}
|
|
};
|
|
|
|
let arena = env.arena;
|
|
let mut output = Vec::with_capacity_in(len, arena);
|
|
let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize;
|
|
|
|
for index in 0..len {
|
|
let offset_bytes = index * elem_size;
|
|
let elem_ptr = unsafe { ptr.add(offset_bytes) };
|
|
let loc_expr = &*arena.alloc(Located {
|
|
value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content),
|
|
region: Region::zero(),
|
|
});
|
|
|
|
output.push(loc_expr);
|
|
}
|
|
|
|
let output = output.into_bump_slice();
|
|
|
|
Expr::List(Collection::with_items(output))
|
|
}
|
|
|
|
fn single_tag_union_to_ast<'a>(
|
|
env: &Env<'a, '_>,
|
|
ptr: *const u8,
|
|
field_layouts: &'a [Layout<'a>],
|
|
tag_name: &TagName,
|
|
payload_vars: &[Variable],
|
|
) -> Expr<'a> {
|
|
let arena = env.arena;
|
|
let tag_expr = tag_name_to_expr(env, tag_name);
|
|
|
|
let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr));
|
|
|
|
let output = if field_layouts.len() == payload_vars.len() {
|
|
let it = payload_vars.iter().copied().zip(field_layouts);
|
|
sequence_of_expr(env, ptr as *const u8, it).into_bump_slice()
|
|
} else if field_layouts.is_empty() && !payload_vars.is_empty() {
|
|
// happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped
|
|
let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]);
|
|
sequence_of_expr(env, ptr as *const u8, it).into_bump_slice()
|
|
} else {
|
|
unreachable!()
|
|
};
|
|
|
|
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
|
|
}
|
|
|
|
fn sequence_of_expr<'a, I>(
|
|
env: &Env<'a, '_>,
|
|
ptr: *const u8,
|
|
sequence: I,
|
|
) -> Vec<'a, &'a Located<Expr<'a>>>
|
|
where
|
|
I: Iterator<Item = (Variable, &'a Layout<'a>)>,
|
|
I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>,
|
|
{
|
|
let arena = env.arena;
|
|
let subs = env.subs;
|
|
let mut output = Vec::with_capacity_in(sequence.len(), arena);
|
|
|
|
// We'll advance this as we iterate through the fields
|
|
let mut field_ptr = ptr as *const u8;
|
|
|
|
for (var, layout) in sequence {
|
|
let content = subs.get_content_without_compacting(var);
|
|
let expr = ptr_to_ast(env, field_ptr, layout, content);
|
|
let loc_expr = Located::at_zero(expr);
|
|
|
|
output.push(&*arena.alloc(loc_expr));
|
|
|
|
// Advance the field pointer to the next field.
|
|
field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.ptr_bytes) as isize) };
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
fn struct_to_ast<'a>(
|
|
env: &Env<'a, '_>,
|
|
ptr: *const u8,
|
|
field_layouts: &'a [Layout<'a>],
|
|
record_fields: RecordFields,
|
|
) -> Expr<'a> {
|
|
let arena = env.arena;
|
|
let subs = env.subs;
|
|
let mut output = Vec::with_capacity_in(field_layouts.len(), arena);
|
|
|
|
let sorted_fields: Vec<_> = Vec::from_iter_in(
|
|
record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD),
|
|
env.arena,
|
|
);
|
|
|
|
if sorted_fields.len() == 1 {
|
|
// this is a 1-field wrapper record around another record or 1-tag tag union
|
|
let (label, field) = sorted_fields.into_iter().next().unwrap();
|
|
|
|
let inner_content = env.subs.get_content_without_compacting(field.into_inner());
|
|
|
|
let loc_expr = &*arena.alloc(Located {
|
|
value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), inner_content),
|
|
region: Region::zero(),
|
|
});
|
|
|
|
let field_name = Located {
|
|
value: &*arena.alloc_str(label.as_str()),
|
|
region: Region::zero(),
|
|
};
|
|
let loc_field = Located {
|
|
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
let output = env.arena.alloc([loc_field]);
|
|
|
|
Expr::Record(Collection::with_items(output))
|
|
} else {
|
|
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
|
|
|
|
// We'll advance this as we iterate through the fields
|
|
let mut field_ptr = ptr;
|
|
|
|
for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) {
|
|
let var = field.into_inner();
|
|
|
|
let content = subs.get_content_without_compacting(var);
|
|
let loc_expr = &*arena.alloc(Located {
|
|
value: ptr_to_ast(env, field_ptr, field_layout, content),
|
|
region: Region::zero(),
|
|
});
|
|
|
|
let field_name = Located {
|
|
value: &*arena.alloc_str(label.as_str()),
|
|
region: Region::zero(),
|
|
};
|
|
let loc_field = Located {
|
|
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
output.push(loc_field);
|
|
|
|
// Advance the field pointer to the next field.
|
|
field_ptr =
|
|
unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
|
|
}
|
|
|
|
let output = output.into_bump_slice();
|
|
|
|
Expr::Record(Collection::with_items(output))
|
|
}
|
|
}
|
|
|
|
fn unpack_single_element_tag_union(subs: &Subs, tags: UnionTags) -> (&TagName, &[Variable]) {
|
|
let (tag_name_index, payload_vars_index) = tags.iter_all().next().unwrap();
|
|
|
|
let tag_name = &subs[tag_name_index];
|
|
let subs_slice = subs[payload_vars_index].as_subs_slice();
|
|
let payload_vars = subs.get_subs_slice(*subs_slice);
|
|
|
|
(tag_name, payload_vars)
|
|
}
|
|
|
|
fn unpack_two_element_tag_union(
|
|
subs: &Subs,
|
|
tags: UnionTags,
|
|
) -> (&TagName, &[Variable], &TagName, &[Variable]) {
|
|
let mut it = tags.iter_all();
|
|
let (tag_name_index, payload_vars_index) = it.next().unwrap();
|
|
|
|
let tag_name1 = &subs[tag_name_index];
|
|
let subs_slice = subs[payload_vars_index].as_subs_slice();
|
|
let payload_vars1 = subs.get_subs_slice(*subs_slice);
|
|
|
|
let (tag_name_index, payload_vars_index) = it.next().unwrap();
|
|
|
|
let tag_name2 = &subs[tag_name_index];
|
|
let subs_slice = subs[payload_vars_index].as_subs_slice();
|
|
let payload_vars2 = subs.get_subs_slice(*subs_slice);
|
|
|
|
(tag_name1, payload_vars1, tag_name2, payload_vars2)
|
|
}
|
|
|
|
fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> {
|
|
use Content::*;
|
|
|
|
let arena = env.arena;
|
|
|
|
match content {
|
|
Structure(flat_type) => {
|
|
match flat_type {
|
|
FlatType::Record(fields, _) => {
|
|
debug_assert_eq!(fields.len(), 1);
|
|
|
|
let (label, field) = fields
|
|
.sorted_iterator(env.subs, Variable::EMPTY_RECORD)
|
|
.next()
|
|
.unwrap();
|
|
|
|
let loc_label = Located {
|
|
value: &*arena.alloc_str(label.as_str()),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
let assigned_field = {
|
|
// We may be multiple levels deep in nested tag unions
|
|
// and/or records (e.g. { a: { b: { c: True } } }),
|
|
// so we need to do this recursively on the field type.
|
|
let field_var = *field.as_inner();
|
|
let field_content = env.subs.get_content_without_compacting(field_var);
|
|
let loc_expr = Located {
|
|
value: bool_to_ast(env, value, field_content),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
|
|
};
|
|
|
|
let loc_assigned_field = Located {
|
|
value: assigned_field,
|
|
region: Region::zero(),
|
|
};
|
|
|
|
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
|
|
}
|
|
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
|
|
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
|
|
|
let loc_tag_expr = {
|
|
let tag_name = &tag_name.as_ident_str(env.interns, env.home);
|
|
let tag_expr = if tag_name.starts_with('@') {
|
|
Expr::PrivateTag(arena.alloc_str(tag_name))
|
|
} else {
|
|
Expr::GlobalTag(arena.alloc_str(tag_name))
|
|
};
|
|
|
|
&*arena.alloc(Located {
|
|
value: tag_expr,
|
|
region: Region::zero(),
|
|
})
|
|
};
|
|
|
|
let payload = {
|
|
// Since this has the layout of a number, there should be
|
|
// exactly one payload in this tag.
|
|
debug_assert_eq!(payload_vars.len(), 1);
|
|
|
|
let var = *payload_vars.iter().next().unwrap();
|
|
let content = env.subs.get_content_without_compacting(var);
|
|
|
|
let loc_payload = &*arena.alloc(Located {
|
|
value: bool_to_ast(env, value, content),
|
|
region: Region::zero(),
|
|
});
|
|
|
|
arena.alloc([loc_payload])
|
|
};
|
|
|
|
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
|
|
}
|
|
FlatType::TagUnion(tags, _) if tags.len() == 2 => {
|
|
let (tag_name_1, payload_vars_1, tag_name_2, payload_vars_2) =
|
|
unpack_two_element_tag_union(env.subs, *tags);
|
|
|
|
debug_assert!(payload_vars_1.is_empty());
|
|
debug_assert!(payload_vars_2.is_empty());
|
|
|
|
let tag_name = if value {
|
|
max_by_key(tag_name_1, tag_name_2, |n| {
|
|
n.as_ident_str(env.interns, env.home)
|
|
})
|
|
} else {
|
|
min_by_key(tag_name_1, tag_name_2, |n| {
|
|
n.as_ident_str(env.interns, env.home)
|
|
})
|
|
};
|
|
|
|
tag_name_to_expr(env, tag_name)
|
|
}
|
|
other => {
|
|
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
|
}
|
|
}
|
|
}
|
|
Alias(_, _, var) => {
|
|
let content = env.subs.get_content_without_compacting(*var);
|
|
|
|
bool_to_ast(env, value, content)
|
|
}
|
|
other => {
|
|
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> {
|
|
use Content::*;
|
|
|
|
let arena = env.arena;
|
|
|
|
match content {
|
|
Structure(flat_type) => {
|
|
match flat_type {
|
|
FlatType::Record(fields, _) => {
|
|
debug_assert_eq!(fields.len(), 1);
|
|
|
|
let (label, field) = fields
|
|
.sorted_iterator(env.subs, Variable::EMPTY_RECORD)
|
|
.next()
|
|
.unwrap();
|
|
|
|
let loc_label = Located {
|
|
value: &*arena.alloc_str(label.as_str()),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
let assigned_field = {
|
|
// We may be multiple levels deep in nested tag unions
|
|
// and/or records (e.g. { a: { b: { c: True } } }),
|
|
// so we need to do this recursively on the field type.
|
|
let field_var = *field.as_inner();
|
|
let field_content = env.subs.get_content_without_compacting(field_var);
|
|
let loc_expr = Located {
|
|
value: byte_to_ast(env, value, field_content),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
|
|
};
|
|
|
|
let loc_assigned_field = Located {
|
|
value: assigned_field,
|
|
region: Region::zero(),
|
|
};
|
|
|
|
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
|
|
}
|
|
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
|
|
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
|
|
|
let loc_tag_expr = {
|
|
let tag_name = &tag_name.as_ident_str(env.interns, env.home);
|
|
let tag_expr = if tag_name.starts_with('@') {
|
|
Expr::PrivateTag(arena.alloc_str(tag_name))
|
|
} else {
|
|
Expr::GlobalTag(arena.alloc_str(tag_name))
|
|
};
|
|
|
|
&*arena.alloc(Located {
|
|
value: tag_expr,
|
|
region: Region::zero(),
|
|
})
|
|
};
|
|
|
|
let payload = {
|
|
// Since this has the layout of a number, there should be
|
|
// exactly one payload in this tag.
|
|
debug_assert_eq!(payload_vars.len(), 1);
|
|
|
|
let var = *payload_vars.iter().next().unwrap();
|
|
let content = env.subs.get_content_without_compacting(var);
|
|
|
|
let loc_payload = &*arena.alloc(Located {
|
|
value: byte_to_ast(env, value, content),
|
|
region: Region::zero(),
|
|
});
|
|
|
|
arena.alloc([loc_payload])
|
|
};
|
|
|
|
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
|
|
}
|
|
FlatType::TagUnion(tags, _) => {
|
|
// anything with fewer tags is not a byte
|
|
debug_assert!(tags.len() > 2);
|
|
|
|
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = tags
|
|
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
|
|
.map(|(a, b)| (a.clone(), b.to_vec()))
|
|
.collect();
|
|
|
|
let union_variant =
|
|
union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes);
|
|
|
|
match union_variant {
|
|
UnionVariant::ByteUnion(tagnames) => {
|
|
let tag_name = &tagnames[value as usize];
|
|
let tag_expr = tag_name_to_expr(env, tag_name);
|
|
let loc_tag_expr = Located::at_zero(tag_expr);
|
|
Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space)
|
|
}
|
|
_ => unreachable!("invalid union variant for a Byte!"),
|
|
}
|
|
}
|
|
other => {
|
|
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
|
}
|
|
}
|
|
}
|
|
Alias(_, _, var) => {
|
|
let content = env.subs.get_content_without_compacting(*var);
|
|
|
|
byte_to_ast(env, value, content)
|
|
}
|
|
other => {
|
|
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> {
|
|
use Content::*;
|
|
|
|
let arena = env.arena;
|
|
|
|
match content {
|
|
Structure(flat_type) => {
|
|
match flat_type {
|
|
FlatType::Apply(Symbol::NUM_NUM, _) => num_expr,
|
|
FlatType::Record(fields, _) => {
|
|
// This was a single-field record that got unwrapped at runtime.
|
|
// Even if it was an i64 at runtime, we still need to report
|
|
// it as a record with the correct field name!
|
|
// Its type signature will tell us that.
|
|
debug_assert_eq!(fields.len(), 1);
|
|
|
|
let (label, field) = fields
|
|
.sorted_iterator(env.subs, Variable::EMPTY_RECORD)
|
|
.next()
|
|
.unwrap();
|
|
|
|
let loc_label = Located {
|
|
value: &*arena.alloc_str(label.as_str()),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
let assigned_field = {
|
|
// We may be multiple levels deep in nested tag unions
|
|
// and/or records (e.g. { a: { b: { c: 5 } } }),
|
|
// so we need to do this recursively on the field type.
|
|
let field_var = *field.as_inner();
|
|
let field_content = env.subs.get_content_without_compacting(field_var);
|
|
let loc_expr = Located {
|
|
value: num_to_ast(env, num_expr, field_content),
|
|
region: Region::zero(),
|
|
};
|
|
|
|
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
|
|
};
|
|
let loc_assigned_field = Located {
|
|
value: assigned_field,
|
|
region: Region::zero(),
|
|
};
|
|
|
|
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
|
|
}
|
|
FlatType::TagUnion(tags, _) => {
|
|
// This was a single-tag union that got unwrapped at runtime.
|
|
debug_assert_eq!(tags.len(), 1);
|
|
|
|
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
|
|
|
// If this tag union represents a number, skip right to
|
|
// returning tis as an Expr::Num
|
|
if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name {
|
|
return num_expr;
|
|
}
|
|
|
|
let loc_tag_expr = {
|
|
let tag_name = &tag_name.as_ident_str(env.interns, env.home);
|
|
let tag_expr = if tag_name.starts_with('@') {
|
|
Expr::PrivateTag(arena.alloc_str(tag_name))
|
|
} else {
|
|
Expr::GlobalTag(arena.alloc_str(tag_name))
|
|
};
|
|
|
|
&*arena.alloc(Located {
|
|
value: tag_expr,
|
|
region: Region::zero(),
|
|
})
|
|
};
|
|
|
|
let payload = {
|
|
// Since this has the layout of a number, there should be
|
|
// exactly one payload in this tag.
|
|
debug_assert_eq!(payload_vars.len(), 1);
|
|
|
|
let var = *payload_vars.iter().next().unwrap();
|
|
let content = env.subs.get_content_without_compacting(var);
|
|
|
|
let loc_payload = &*arena.alloc(Located {
|
|
value: num_to_ast(env, num_expr, content),
|
|
region: Region::zero(),
|
|
});
|
|
|
|
arena.alloc([loc_payload])
|
|
};
|
|
|
|
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
|
|
}
|
|
other => {
|
|
panic!("Unexpected FlatType {:?} in num_to_ast", other);
|
|
}
|
|
}
|
|
}
|
|
Alias(_, _, var) => {
|
|
let content = env.subs.get_content_without_compacting(*var);
|
|
|
|
num_to_ast(env, num_expr, content)
|
|
}
|
|
other => {
|
|
panic!("Unexpected FlatType {:?} in num_to_ast", other);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This is centralized in case we want to format it differently later,
|
|
/// e.g. adding underscores for large numbers
|
|
fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_> {
|
|
Expr::Num(arena.alloc(format!("{}", num)))
|
|
}
|
|
|
|
#[cfg(target_endian = "little")]
|
|
#[cfg(target_pointer_width = "64")]
|
|
/// TODO implement this for 32-bit and big-endian targets. NOTE: As of this writing,
|
|
/// we don't have big-endian small strings implemented yet!
|
|
fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> {
|
|
let bytes: [u8; 16] = unsafe { std::mem::transmute::<&'a str, [u8; 16]>(string) };
|
|
let is_small = (bytes[15] & 0b1000_0000) != 0;
|
|
|
|
if is_small {
|
|
let len = (bytes[15] & 0b0111_1111) as usize;
|
|
let mut string = bumpalo::collections::String::with_capacity_in(len, arena);
|
|
|
|
for byte in bytes.iter().take(len) {
|
|
string.push(*byte as char);
|
|
}
|
|
|
|
str_slice_to_ast(arena, arena.alloc(string))
|
|
} else {
|
|
// Roc string literals are stored inside the constant section of the program
|
|
// That means this memory is gone when the jit function is done
|
|
// (as opposed to heap memory, which we can leak and then still use after)
|
|
// therefore we must make an owned copy of the string here
|
|
let string = bumpalo::collections::String::from_str_in(string, arena).into_bump_str();
|
|
str_slice_to_ast(arena, string)
|
|
}
|
|
}
|
|
|
|
fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
|
|
if string.contains('\n') {
|
|
todo!(
|
|
"this string contains newlines, so render it as a multiline string: {:?}",
|
|
Expr::Str(StrLiteral::PlainLine(string))
|
|
);
|
|
} else {
|
|
Expr::Str(StrLiteral::PlainLine(string))
|
|
}
|
|
}
|
|
|
|
// TODO this is currently nighly-only: use the implementation in std once it's stabilized
|
|
pub fn max_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
|
|
use std::cmp::Ordering;
|
|
|
|
match compare(&v1, &v2) {
|
|
Ordering::Less | Ordering::Equal => v2,
|
|
Ordering::Greater => v1,
|
|
}
|
|
}
|
|
|
|
pub fn min_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
|
|
use std::cmp::Ordering;
|
|
|
|
match compare(&v1, &v2) {
|
|
Ordering::Less | Ordering::Equal => v1,
|
|
Ordering::Greater => v2,
|
|
}
|
|
}
|
|
|
|
pub fn max_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
|
|
max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
|
|
}
|
|
|
|
pub fn min_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
|
|
min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
|
|
}
|