Merge branch 'Frame-Limited' into joinpoint-reuse

This commit is contained in:
J.Teeuwissen 2023-03-30 20:38:30 +02:00
commit 36f4969a5c
No known key found for this signature in database
GPG key ID: DB5F7A1ED8D478AD
591 changed files with 42667 additions and 28227 deletions

View file

@ -1,14 +1,17 @@
[package]
authors = ["The Roc Contributors"]
edition = "2021"
license = "UPL-1.0"
name = "roc_alias_analysis"
version = "0.0.1"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
morphic_lib = {path = "../../vendor/morphic_lib"}
roc_collections = {path = "../collections"}
roc_module = {path = "../module"}
roc_mono = {path = "../mono"}
roc_debug_flags = {path = "../debug_flags"}
morphic_lib = { path = "../../vendor/morphic_lib" }
roc_collections = { path = "../collections" }
roc_debug_flags = { path = "../debug_flags" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
bumpalo.workspace = true

View file

@ -9,6 +9,7 @@ use morphic_lib::{
TypeDefBuilder, TypeId, TypeName, UpdateModeVar, ValueId,
};
use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
@ -187,20 +188,28 @@ where
let func_name = FuncName(&bytes);
if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts {
for (_, (symbol, top_level, layout)) in aliases {
match layout {
for (_, hels) in aliases {
match hels.raw_function_layout {
RawFunctionLayout::Function(_, _, _) => {
let it = top_level.arguments.iter().copied();
let bytes =
func_name_bytes_help(*symbol, it, Niche::NONE, top_level.result);
let it = hels.proc_layout.arguments.iter().copied();
let bytes = func_name_bytes_help(
hels.symbol,
it,
Niche::NONE,
hels.proc_layout.result,
);
host_exposed_functions.push((bytes, top_level.arguments));
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
let bytes =
func_name_bytes_help(*symbol, [], Niche::NONE, top_level.result);
let bytes = func_name_bytes_help(
hels.symbol,
[],
Niche::NONE,
hels.proc_layout.result,
);
host_exposed_functions.push((bytes, top_level.arguments));
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
}
}
@ -365,13 +374,7 @@ fn build_entry_point<'a>(
let block = builder.add_block();
// to the modelling language, the arguments appear out of thin air
let argument_type = build_tuple_type(
env,
&mut builder,
interner,
layout.arguments,
&WhenRecursive::Unreachable,
)?;
let argument_type = build_tuple_type(env, &mut builder, interner, layout.arguments)?;
// does not make any assumptions about the input
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
@ -401,13 +404,7 @@ fn build_entry_point<'a>(
let block = builder.add_block();
let struct_layout = interner.insert(Layout::struct_no_name_order(layouts));
let type_id = layout_spec(
env,
&mut builder,
interner,
struct_layout,
&WhenRecursive::Unreachable,
)?;
let type_id = layout_spec(env, &mut builder, interner, struct_layout)?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@ -466,20 +463,8 @@ fn proc_spec<'a>(
let args_struct_layout = interner.insert(Layout::struct_no_name_order(
argument_layouts.into_bump_slice(),
));
let arg_type_id = layout_spec(
&mut env,
&mut builder,
interner,
args_struct_layout,
&WhenRecursive::Unreachable,
)?;
let ret_type_id = layout_spec(
&mut env,
&mut builder,
interner,
proc.ret_layout,
&WhenRecursive::Unreachable,
)?;
let arg_type_id = layout_spec(&mut env, &mut builder, interner, args_struct_layout)?;
let ret_type_id = layout_spec(&mut env, &mut builder, interner, proc.ret_layout)?;
let spec = builder.build(arg_type_id, ret_type_id, root)?;
@ -617,17 +602,10 @@ fn stmt_spec<'a>(
let mut type_ids = Vec::new();
for p in parameters.iter() {
type_ids.push(layout_spec(
env,
builder,
interner,
p.layout,
&WhenRecursive::Unreachable,
)?);
type_ids.push(layout_spec(env, builder, interner, p.layout)?);
}
let ret_type_id =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
let ret_type_id = layout_spec(env, builder, interner, layout)?;
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
@ -668,8 +646,7 @@ fn stmt_spec<'a>(
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
}
Jump(id, symbols) => {
let ret_type_id =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
let ret_type_id = layout_spec(env, builder, interner, layout)?;
let argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id];
@ -678,8 +655,7 @@ fn stmt_spec<'a>(
Crash(msg, _) => {
// Model this as a foreign call rather than TERMINATE because
// we want ownership of the message.
let result_type =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
let result_type = layout_spec(env, builder, interner, layout)?;
builder.add_unknown_with(block, &[env.symbols[msg]], result_type)
}
@ -708,23 +684,16 @@ fn build_tuple_value(
builder.add_make_tuple(block, &value_ids)
}
#[derive(Clone, Debug, PartialEq)]
enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),
}
fn build_recursive_tuple_type<'a>(
env: &mut Env<'a>,
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layouts: &[InLayout<'a>],
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
let mut field_types = Vec::new();
for field in layouts.iter() {
let type_id = layout_spec_help(env, builder, interner, *field, when_recursive)?;
let type_id = layout_spec_help(env, builder, interner, *field)?;
field_types.push(type_id);
}
@ -736,12 +705,11 @@ fn build_tuple_type<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layouts: &[InLayout<'a>],
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
let mut field_types = Vec::new();
for field in layouts.iter() {
field_types.push(layout_spec(env, builder, interner, *field, when_recursive)?);
field_types.push(layout_spec(env, builder, interner, *field)?);
}
builder.add_tuple_type(&field_types)
@ -811,13 +779,7 @@ fn call_spec<'a>(
.map(|symbol| env.symbols[symbol])
.collect();
let result_type = layout_spec(
env,
builder,
interner,
*ret_layout,
&WhenRecursive::Unreachable,
)?;
let result_type = layout_spec(env, builder, interner, *ret_layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -888,23 +850,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(
env,
builder,
interner,
*return_layout,
&WhenRecursive::Unreachable,
)?;
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
let state_type = layout_spec(
env,
builder,
interner,
state_layout,
&WhenRecursive::Unreachable,
)?;
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -931,13 +881,7 @@ fn call_spec<'a>(
let arg0_layout = argument_layouts[0];
let state_layout = interner.insert(Layout::Builtin(Builtin::List(arg0_layout)));
let state_type = layout_spec(
env,
builder,
interner,
state_layout,
&WhenRecursive::Unreachable,
)?;
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@ -961,23 +905,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(
env,
builder,
interner,
*return_layout,
&WhenRecursive::Unreachable,
)?;
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
let state_type = layout_spec(
env,
builder,
interner,
state_layout,
&WhenRecursive::Unreachable,
)?;
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -1007,23 +939,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(
env,
builder,
interner,
*return_layout,
&WhenRecursive::Unreachable,
)?;
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
let state_type = layout_spec(
env,
builder,
interner,
state_layout,
&WhenRecursive::Unreachable,
)?;
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -1059,23 +979,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(
env,
builder,
interner,
*return_layout,
&WhenRecursive::Unreachable,
)?;
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
let state_type = layout_spec(
env,
builder,
interner,
state_layout,
&WhenRecursive::Unreachable,
)?;
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -1130,7 +1038,7 @@ fn lowlevel_spec<'a>(
) -> Result<ValueId> {
use LowLevel::*;
let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
let type_id = layout_spec(env, builder, interner, layout)?;
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
@ -1238,13 +1146,7 @@ fn lowlevel_spec<'a>(
match interner.get(layout) {
Layout::Builtin(Builtin::List(element_layout)) => {
let type_id = layout_spec(
env,
builder,
interner,
element_layout,
&WhenRecursive::Unreachable,
)?;
let type_id = layout_spec(env, builder, interner, element_layout)?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
@ -1255,6 +1157,11 @@ fn lowlevel_spec<'a>(
list_clone(builder, block, update_mode_var, list)
}
ListReleaseExcessCapacity => {
let list = env.symbols[&arguments[0]];
list_clone(builder, block, update_mode_var, list)
}
ListAppendUnsafe => {
let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[1]];
@ -1287,8 +1194,7 @@ fn lowlevel_spec<'a>(
// TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
let result_type =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
let result_type = layout_spec(env, builder, interner, layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -1299,12 +1205,9 @@ fn recursive_tag_variant<'a>(
env: &mut Env<'a>,
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
union_layout: &UnionLayout,
fields: &[InLayout<'a>],
) -> Result<TypeId> {
let when_recursive = WhenRecursive::Loop(*union_layout);
build_recursive_tuple_type(env, builder, interner, fields, &when_recursive)
build_recursive_tuple_type(env, builder, interner, fields)
}
fn recursive_variant_types<'a>(
@ -1325,23 +1228,11 @@ fn recursive_variant_types<'a>(
result = Vec::with_capacity(tags.len());
for tag in tags.iter() {
result.push(recursive_tag_variant(
env,
builder,
interner,
union_layout,
tag,
)?);
result.push(recursive_tag_variant(env, builder, interner, tag)?);
}
}
NonNullableUnwrapped(fields) => {
result = vec![recursive_tag_variant(
env,
builder,
interner,
union_layout,
fields,
)?];
result = vec![recursive_tag_variant(env, builder, interner, fields)?];
}
NullableWrapped {
nullable_id,
@ -1352,39 +1243,21 @@ fn recursive_variant_types<'a>(
let cutoff = *nullable_id as usize;
for tag in tags[..cutoff].iter() {
result.push(recursive_tag_variant(
env,
builder,
interner,
union_layout,
tag,
)?);
result.push(recursive_tag_variant(env, builder, interner, tag)?);
}
result.push(recursive_tag_variant(
env,
builder,
interner,
union_layout,
&[],
)?);
result.push(recursive_tag_variant(env, builder, interner, &[])?);
for tag in tags[cutoff..].iter() {
result.push(recursive_tag_variant(
env,
builder,
interner,
union_layout,
tag,
)?);
result.push(recursive_tag_variant(env, builder, interner, tag)?);
}
}
NullableUnwrapped {
nullable_id,
other_fields: fields,
} => {
let unit = recursive_tag_variant(env, builder, interner, union_layout, &[])?;
let other_type = recursive_tag_variant(env, builder, interner, union_layout, fields)?;
let unit = recursive_tag_variant(env, builder, interner, &[])?;
let other_type = recursive_tag_variant(env, builder, interner, fields)?;
if *nullable_id {
// nullable_id == 1
@ -1432,13 +1305,7 @@ fn expr_spec<'a>(
let value_id = match tag_layout {
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(
env,
builder,
interner,
tags,
&WhenRecursive::Unreachable,
)?;
let variant_types = non_recursive_variant_types(env, builder, interner, tags)?;
let value_id = build_tuple_value(builder, env, block, arguments)?;
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
}
@ -1543,13 +1410,7 @@ fn expr_spec<'a>(
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Array { elem_layout, elems } => {
let type_id = layout_spec(
env,
builder,
interner,
*elem_layout,
&WhenRecursive::Unreachable,
)?;
let type_id = layout_spec(env, builder, interner, *elem_layout)?;
let list = new_list(builder, block, type_id)?;
@ -1576,13 +1437,7 @@ fn expr_spec<'a>(
EmptyArray => match interner.get(layout) {
Layout::Builtin(Builtin::List(element_layout)) => {
let type_id = layout_spec(
env,
builder,
interner,
element_layout,
&WhenRecursive::Unreachable,
)?;
let type_id = layout_spec(env, builder, interner, element_layout)?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
@ -1615,7 +1470,7 @@ fn expr_spec<'a>(
with_new_heap_cell(builder, block, union_data)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
let type_id = layout_spec(env, builder, interner, layout)?;
builder.add_terminate(block, type_id)
}
@ -1647,9 +1502,8 @@ fn layout_spec<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
layout_spec_help(env, builder, interner, layout, when_recursive)
layout_spec_help(env, builder, interner, layout)
}
fn non_recursive_variant_types<'a>(
@ -1657,19 +1511,11 @@ fn non_recursive_variant_types<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
tags: &[&[InLayout<'a>]],
// If there is a recursive pointer latent within this layout, coming from a containing layout.
when_recursive: &WhenRecursive,
) -> Result<Vec<TypeId>> {
let mut result = Vec::with_capacity(tags.len());
for tag in tags.iter() {
result.push(build_tuple_type(
env,
builder,
interner,
tag,
when_recursive,
)?);
result.push(build_tuple_type(env, builder, interner, tag)?);
}
Ok(result)
@ -1680,22 +1526,17 @@ fn layout_spec_help<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
use Layout::*;
match interner.get(layout) {
Builtin(builtin) => builtin_spec(env, builder, interner, &builtin, when_recursive),
Builtin(builtin) => builtin_spec(env, builder, interner, &builtin),
Struct { field_layouts, .. } => {
build_recursive_tuple_type(env, builder, interner, field_layouts, when_recursive)
build_recursive_tuple_type(env, builder, interner, field_layouts)
}
LambdaSet(lambda_set) => {
layout_spec_help(env, builder, interner, lambda_set.runtime_representation())
}
LambdaSet(lambda_set) => layout_spec_help(
env,
builder,
interner,
lambda_set.runtime_representation(),
when_recursive,
),
Union(union_layout) => {
match union_layout {
UnionLayout::NonRecursive(&[]) => {
@ -1705,8 +1546,7 @@ fn layout_spec_help<'a>(
builder.add_tuple_type(&[])
}
UnionLayout::NonRecursive(tags) => {
let variant_types =
non_recursive_variant_types(env, builder, interner, tags, when_recursive)?;
let variant_types = non_recursive_variant_types(env, builder, interner, tags)?;
builder.add_union_type(&variant_types)
}
UnionLayout::Recursive(_)
@ -1724,29 +1564,21 @@ fn layout_spec_help<'a>(
}
Boxed(inner_layout) => {
let inner_type =
layout_spec_help(env, builder, interner, inner_layout, when_recursive)?;
let inner_type = layout_spec_help(env, builder, interner, inner_layout)?;
let cell_type = builder.add_heap_cell_type();
builder.add_tuple_type(&[cell_type, inner_type])
}
// TODO(recursive-layouts): update once we have recursive pointer loops
RecursivePointer(_) => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!()
}
WhenRecursive::Loop(union_layout) => match union_layout {
UnionLayout::NonRecursive(_) => unreachable!(),
UnionLayout::Recursive(_)
| UnionLayout::NullableUnwrapped { .. }
| UnionLayout::NullableWrapped { .. }
| UnionLayout::NonNullableUnwrapped(_) => {
let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes);
RecursivePointer(union_layout) => match interner.get(union_layout) {
Layout::Union(union_layout) => {
assert!(!matches!(union_layout, UnionLayout::NonRecursive(..)));
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes);
Ok(builder.add_named_type(MOD_APP, type_name))
}
},
Ok(builder.add_named_type(MOD_APP, type_name))
}
_ => internal_error!("somehow, a non-recursive layout is under a recursive pointer"),
},
}
}
@ -1756,7 +1588,6 @@ fn builtin_spec<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
builtin: &Builtin<'a>,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
use Builtin::*;
@ -1765,8 +1596,7 @@ fn builtin_spec<'a>(
Decimal | Float(_) => builder.add_tuple_type(&[]),
Str => str_type(builder),
List(element_layout) => {
let element_type =
layout_spec_help(env, builder, interner, *element_layout, when_recursive)?;
let element_type = layout_spec_help(env, builder, interner, *element_layout)?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;

View file

@ -1,11 +1,12 @@
[package]
name = "arena-pool"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "An implementation of an arena allocator designed for the compiler's workloads."
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
roc_error_macros = { path = "../../error_macros" }

View file

@ -1,52 +1,55 @@
[package]
name = "roc_build"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Responsible for coordinating building and linking of a Roc app with its host."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_bitcode = { path = "../builtins/bitcode" }
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve_problem = { path = "../solve_problem" }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_target = { path = "../roc_target" }
roc_error_macros = { path = "../../error_macros" }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_gen_llvm = { path = "../gen_llvm" }
roc_gen_wasm = { path = "../gen_wasm" }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_linker = { path = "../../linker" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_packaging = { path = "../../packaging" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_reporting = { path = "../../reporting" }
roc_error_macros = { path = "../../error_macros" }
roc_solve_problem = { path = "../solve_problem" }
roc_std = { path = "../../roc_std" }
roc_utils = { path = "../../utils" }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
roc_command_utils = { path = "../../utils/command" }
wasi_libc_sys = { path = "../../wasi-libc-sys" }
const_format.workspace = true
bumpalo.workspace = true
libloading.workspace = true
tempfile.workspace = true
target-lexicon.workspace = true
indoc.workspace = true
inkwell.workspace = true
libloading.workspace = true
target-lexicon.workspace = true
tempfile.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.85"
serde_json.workspace = true
[features]
target-arm = []
target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-arm = []
target-wasm32 = []
target-x86 = []
target-x86_64 = ["roc_gen_dev/target-x86_64"]
target-wasm32 = []
# This is used to enable fuzzing and sanitizers.
# Example use is describe here: https://github.com/bhansconnect/roc-fuzz

View file

@ -1,26 +1,18 @@
use crate::target::{arch_str, target_zig_str};
use const_format::concatcp;
use libloading::{Error, Library};
use roc_builtins::bitcode;
use roc_command_utils::{cargo, clang, get_lib_path, rustup, zig};
use roc_error_macros::internal_error;
use roc_mono::ir::OptLevel;
use roc_utils::{cargo, clang, zig};
use roc_utils::{get_lib_path, rustup};
use std::collections::HashMap;
use std::env;
use std::fs::DirEntry;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{self, Child, Command};
use std::{env, fs};
use target_lexicon::{Architecture, OperatingSystem, Triple};
use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
// These numbers correspond to the --lib and --no-link flags
Executable = 0,
Dylib = 1,
None = 2,
}
pub use roc_linker::LinkType;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkingStrategy {
@ -60,134 +52,15 @@ pub fn link(
}
}
const PRECOMPILED_HOST_EXT: &str = "rh1"; // Short for "roc host version 1" (so we can change format in the future)
const WASM_TARGET_STR: &str = "wasm32";
const LINUX_X86_64_TARGET_STR: &str = "linux-x86_64";
const LINUX_ARM64_TARGET_STR: &str = "linux-arm64";
const MACOS_ARM64_TARGET_STR: &str = "macos-arm64";
const MACOS_X86_64_TARGET_STR: &str = "macos-x86_64";
const WINDOWS_X86_64_TARGET_STR: &str = "windows-x86_64";
const WINDOWS_X86_32_TARGET_STR: &str = "windows-x86_32";
const WIDNOWS_ARM64_TARGET_STR: &str = "windows-arm64";
pub const fn preprocessed_host_filename(target: &Triple) -> Option<&'static str> {
// Don't try to split this match off in a different function, it will not work with concatcp
match target {
Triple {
architecture: Architecture::Wasm32,
..
} => Some(concatcp!(WASM_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::X86_64,
..
} => Some(concatcp!(
LINUX_X86_64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!(LINUX_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!(MACOS_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::X86_64,
..
} => Some(concatcp!(
MACOS_X86_64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_64,
..
} => Some(concatcp!(
WINDOWS_X86_64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_32(_),
..
} => Some(concatcp!(
WINDOWS_X86_32_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!(
WIDNOWS_ARM64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
_ => None,
}
}
pub fn get_target_triple_str(target: &Triple) -> Option<&'static str> {
match target {
Triple {
architecture: Architecture::Wasm32,
..
} => Some(WASM_TARGET_STR),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::X86_64,
..
} => Some(LINUX_X86_64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::Aarch64(_),
..
} => Some(LINUX_ARM64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::Aarch64(_),
..
} => Some(MACOS_ARM64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::X86_64,
..
} => Some(MACOS_X86_64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_64,
..
} => Some(WINDOWS_X86_64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_32(_),
..
} => Some(WINDOWS_X86_32_TARGET_STR),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::Aarch64(_),
..
} => Some(WIDNOWS_ARM64_TARGET_STR),
_ => None,
}
}
/// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj"
pub fn legacy_host_filename(target: &Triple) -> Option<String> {
let os = roc_target::OperatingSystem::from(target.operating_system);
let ext = os.object_file_ext();
Some(preprocessed_host_filename(target)?.replace(PRECOMPILED_HOST_EXT, ext))
Some(
roc_linker::preprocessed_host_filename(target)?
.replace(roc_linker::PRECOMPILED_HOST_EXT, ext),
)
}
fn find_zig_str_path() -> PathBuf {
@ -681,7 +554,7 @@ pub fn rebuild_host(
let env_cpath = env::var("CPATH").unwrap_or_else(|_| "".to_string());
let builtins_host_tempfile =
bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
if zig_host_src.exists() {
// Compile host.zig
@ -752,14 +625,6 @@ pub fn rebuild_host(
// Compile and link Cargo.toml, if it exists
let cargo_dir = platform_main_roc.parent().unwrap();
let cargo_out_dir = cargo_dir.join("target").join(
if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
"release"
} else {
"debug"
},
);
let mut cargo_cmd = if cfg!(windows) {
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
@ -793,11 +658,20 @@ pub fn rebuild_host(
run_build_command(cargo_cmd, source_file, 0);
let cargo_out_dir = find_used_target_sub_folder(opt_level, cargo_dir.join("target"));
if shared_lib_path.is_some() {
// For surgical linking, just copy the dynamically linked rust app.
let mut exe_path = cargo_out_dir.join("host");
exe_path.set_extension(executable_extension);
std::fs::copy(&exe_path, &host_dest).unwrap();
if let Err(e) = std::fs::copy(&exe_path, &host_dest) {
panic!(
"unable to copy {} => {}: {:?}\n\nIs the file used by another invocation of roc?",
exe_path.display(),
host_dest.display(),
e,
);
}
} else {
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
@ -943,6 +817,63 @@ pub fn rebuild_host(
host_dest
}
// there can be multiple release folders, one in target and one in target/x86_64-unknown-linux-musl,
// we want the one that was most recently used
fn find_used_target_sub_folder(opt_level: OptLevel, target_folder: PathBuf) -> PathBuf {
let out_folder_name = if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
"release"
} else {
"debug"
};
let matching_folders = find_in_folder_or_subfolders(&target_folder, out_folder_name);
let mut matching_folders_iter = matching_folders.iter();
let mut out_folder = match matching_folders_iter.next() {
Some(dir_entry) => dir_entry,
None => panic!("I could not find a folder named {} in {:?}. This may be because the `cargo build` for the platform went wrong.", out_folder_name, target_folder)
};
let mut out_folder_last_change = out_folder.metadata().unwrap().modified().unwrap();
for dir_entry in matching_folders_iter {
let last_modified = dir_entry.metadata().unwrap().modified().unwrap();
if last_modified > out_folder_last_change {
out_folder_last_change = last_modified;
out_folder = dir_entry;
}
}
out_folder.path().canonicalize().unwrap()
}
fn find_in_folder_or_subfolders(path: &PathBuf, folder_to_find: &str) -> Vec<DirEntry> {
let mut matching_dirs = vec![];
if let Ok(entries) = fs::read_dir(path) {
for entry in entries.flatten() {
if entry.file_type().unwrap().is_dir() {
let dir_name = entry
.file_name()
.into_string()
.unwrap_or_else(|_| "".to_string());
if dir_name == folder_to_find {
matching_dirs.push(entry)
} else {
let matched_in_sub_dir =
find_in_folder_or_subfolders(&entry.path(), folder_to_find);
matching_dirs.extend(matched_in_sub_dir);
}
}
}
}
matching_dirs
}
fn get_target_str(target: &Triple) -> &str {
if target.operating_system == OperatingSystem::Windows
&& target.environment == target_lexicon::Environment::Gnu
@ -1502,8 +1433,8 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
(but seems to be an unofficial API)
*/
let builtins_host_tempfile =
bitcode::host_wasm_tempfile().expect("failed to write host builtins object to tempfile");
let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile()
.expect("failed to write host builtins object to tempfile");
let mut zig_cmd = zig();
let args = &[

View file

@ -1,17 +1,36 @@
use crate::link::{
legacy_host_filename, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy,
};
use bumpalo::Bump;
use inkwell::memory_buffer::MemoryBuffer;
use roc_error_macros::internal_error;
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::{EntryPoint, ExpectMetadata, LoadedModule, MonomorphizedModule};
use roc_load::{
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, MonomorphizedModule, Threading,
};
use roc_mono::ir::{OptLevel, SingleEntryPoint};
use roc_reporting::cli::{report_problems, Problems};
use roc_packaging::cache::RocCacheDir;
use roc_reporting::{
cli::{report_problems, Problems},
report::{RenderTarget, DEFAULT_PALETTE},
};
use roc_target::TargetInfo;
use std::ffi::OsStr;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::{
path::{Path, PathBuf},
thread::JoinHandle,
time::{Duration, Instant},
};
use target_lexicon::Triple;
#[cfg(feature = "target-wasm32")]
use roc_collections::all::MutSet;
pub const DEFAULT_ROC_FILENAME: &str = "main.roc";
#[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming {
pub code_gen: Duration,
@ -56,7 +75,7 @@ impl Deref for CodeObject {
#[derive(Debug, Clone, Copy)]
pub enum CodeGenBackend {
Assembly,
Llvm,
Llvm(LlvmBackendMode),
Wasm,
}
@ -79,6 +98,10 @@ pub fn gen_from_mono_module<'a>(
preprocessed_host_path: &Path,
wasm_dev_stack_bytes: Option<u32>,
) -> GenFromMono<'a> {
let path = roc_file_path;
let debug = code_gen_options.emit_debug_info;
let opt = code_gen_options.opt_level;
match code_gen_options.backend {
CodeGenBackend::Assembly => gen_from_mono_module_dev(
arena,
@ -87,12 +110,18 @@ pub fn gen_from_mono_module<'a>(
preprocessed_host_path,
wasm_dev_stack_bytes,
),
CodeGenBackend::Llvm => {
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
CodeGenBackend::Llvm(backend_mode) => {
gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug)
}
CodeGenBackend::Wasm => {
// emit wasm via the llvm backend
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
let backend_mode = match code_gen_options.opt_level {
OptLevel::Development => LlvmBackendMode::BinaryDev,
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
};
gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug)
}
}
}
@ -105,7 +134,9 @@ fn gen_from_mono_module_llvm<'a>(
mut loaded: MonomorphizedModule<'a>,
roc_file_path: &Path,
target: &target_lexicon::Triple,
code_gen_options: CodeGenOptions,
opt_level: OptLevel,
backend_mode: LlvmBackendMode,
emit_debug_info: bool,
) -> GenFromMono<'a> {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
@ -155,12 +186,6 @@ fn gen_from_mono_module_llvm<'a>(
}
}
let CodeGenOptions {
backend: _,
opt_level,
emit_debug_info,
} = code_gen_options;
let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
@ -175,12 +200,14 @@ fn gen_from_mono_module_llvm<'a>(
interns: loaded.interns,
module,
target_info,
mode: match opt_level {
OptLevel::Development => LlvmBackendMode::BinaryDev,
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
},
mode: backend_mode,
exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(),
exposed_to_host: loaded
.exposed_to_host
.top_level_values
.keys()
.copied()
.collect(),
};
// does not add any externs for this mode (we have a host) but cleans up some functions around
@ -208,6 +235,7 @@ fn gen_from_mono_module_llvm<'a>(
loaded.procedures,
entry_point,
Some(&app_ll_file),
&loaded.glue_layouts,
);
env.dibuilder.finalize();
@ -484,7 +512,7 @@ fn gen_from_mono_module_dev_wasm32<'a>(
let exposed_to_host = loaded
.exposed_to_host
.values
.top_level_values
.keys()
.copied()
.collect::<MutSet<_>>();
@ -555,7 +583,7 @@ fn gen_from_mono_module_dev_assembly<'a>(
let env = roc_gen_dev::Env {
arena,
module_id,
exposed_to_host: exposed_to_host.values.keys().copied().collect(),
exposed_to_host: exposed_to_host.top_level_values.keys().copied().collect(),
lazy_literals,
generate_allocators,
};
@ -579,3 +607,675 @@ fn gen_from_mono_module_dev_assembly<'a>(
},
)
}
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
use std::fmt::Write;
writeln!(
buf,
" {:9.3} ms {}",
duration.as_secs_f64() * 1000.0,
label,
)
.unwrap()
}
pub struct BuiltFile<'a> {
pub binary_path: PathBuf,
pub problems: Problems,
pub total_time: Duration,
pub expect_metadata: ExpectMetadata<'a>,
}
pub enum BuildOrdering {
/// Run up through typechecking first; continue building iff that is successful.
BuildIfChecks,
/// Always build the Roc binary, even if there are type errors.
AlwaysBuild,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum BuildFileError<'a> {
LoadingProblem(LoadingProblem<'a>),
ErrorModule {
module: LoadedModule,
total_time: Duration,
},
}
impl<'a> BuildFileError<'a> {
fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self {
match error {
LoadMonomorphizedError::LoadingProblem(problem) => {
BuildFileError::LoadingProblem(problem)
}
LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule {
module,
total_time: compilation_start.elapsed(),
},
}
}
}
pub fn handle_error_module(
mut module: roc_load::LoadedModule,
total_time: std::time::Duration,
filename: &OsStr,
print_run_anyway_hint: bool,
) -> std::io::Result<i32> {
debug_assert!(module.total_problems() > 0);
let problems = report_problems_typechecked(&mut module);
problems.print_to_stdout(total_time);
if print_run_anyway_hint {
// If you're running "main.roc" then you can just do `roc run`
// to re-run the program.
print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
if filename != DEFAULT_ROC_FILENAME {
print!(" {}", &filename.to_string_lossy());
}
println!("\x1B[39m");
}
Ok(problems.exit_code())
}
pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result<i32> {
match problem {
LoadingProblem::FormattedReport(report) => {
print!("{}", report);
Ok(1)
}
_ => {
// TODO: tighten up the types here, we should always end up with a
// formatted report from load.
print!("Failed with error: {:?}", problem);
Ok(1)
}
}
}
pub fn standard_load_config(
target: &Triple,
order: BuildOrdering,
threading: Threading,
) -> LoadConfig {
let target_info = TargetInfo::from(target);
let exec_mode = match order {
BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
LoadConfig {
target_info,
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
exec_mode,
}
}
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
wasm_dev_stack_bytes: Option<u32>,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
// Step 1: compile the app and generate the .o file
let loaded =
roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
build_loaded_file(
arena,
target,
app_module_path,
code_gen_options,
emit_timings,
link_type,
linking_strategy,
prebuilt_requested,
wasm_dev_stack_bytes,
loaded,
compilation_start,
)
}
#[allow(clippy::too_many_arguments)]
fn build_loaded_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
wasm_dev_stack_bytes: Option<u32>,
loaded: roc_load::MonomorphizedModule<'a>,
compilation_start: Instant,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let operating_system = roc_target::OperatingSystem::from(target.operating_system);
let platform_main_roc = match &loaded.entry_point {
EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
_ => unreachable!(),
};
// the preprocessed host is stored beside the platform's main.roc
let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
if let roc_target::OperatingSystem::Wasi = operating_system {
// when compiling a wasm application, we implicitly assume here that the host is in zig
// and has a file called "host.zig"
platform_main_roc.with_file_name("host.zig")
} else {
platform_main_roc.with_file_name(legacy_host_filename(target).unwrap())
}
} else {
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap())
};
// For example, if we're loading the platform from a URL, it's automatically prebuilt
// even if the --prebuilt-platform=true CLI flag wasn't set.
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
let cwd = app_module_path.parent().unwrap();
let mut output_exe_path = cwd.join(&*loaded.output_path);
if let Some(extension) = operating_system.executable_file_ext() {
output_exe_path.set_extension(extension);
}
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
None
} else if is_platform_prebuilt {
if !preprocessed_host_path.exists() {
invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
std::process::exit(1);
}
if linking_strategy == LinkingStrategy::Surgical {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
None
} else {
// TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get prebuilt platforms from there.
let dll_stub_symbols = roc_linker::ExposedSymbols::from_exposed_to_host(
&loaded.interns,
&loaded.exposed_to_host,
);
let join_handle = spawn_rebuild_thread(
code_gen_options.opt_level,
linking_strategy,
platform_main_roc.clone(),
preprocessed_host_path.clone(),
output_exe_path.clone(),
target,
dll_stub_symbols,
);
Some(join_handle)
};
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
use std::fmt::Write;
write!(buf, "{}", module_timing).unwrap();
if it.peek().is_some() {
buf.push('\n');
}
}
// This only needs to be mutable for report_problems. This can't be done
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
let problems = report_problems_monomorphized(&mut loaded);
let loaded = loaded;
enum HostRebuildTiming {
BeforeApp(u128),
ConcurrentWithApp(JoinHandle<u128>),
}
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
}
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
} else {
Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
}
} else {
None
};
let (roc_app_bytes, code_gen_timing, expect_metadata) = gen_from_mono_module(
arena,
loaded,
&app_module_path,
target,
code_gen_options,
&preprocessed_host_path,
wasm_dev_stack_bytes,
);
buf.push('\n');
buf.push_str(" ");
buf.push_str("Code Generation");
buf.push('\n');
report_timing(
buf,
"Generate Assembly from Mono IR",
code_gen_timing.code_gen,
);
let compilation_end = compilation_start.elapsed();
let size = roc_app_bytes.len();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
}
if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
}
}
// Step 2: link the prebuilt platform and compiled app
let link_start = Instant::now();
match (linking_strategy, link_type) {
(LinkingStrategy::Surgical, _) => {
roc_linker::link_preprocessed_host(
target,
&platform_main_roc,
&roc_app_bytes,
&output_exe_path,
);
}
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
// Just copy the object file to the output folder.
output_exe_path.set_extension(operating_system.object_file_ext());
std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap();
}
(LinkingStrategy::Legacy, _) => {
let app_o_file = tempfile::Builder::new()
.prefix("roc_app")
.suffix(&format!(".{}", operating_system.object_file_ext()))
.tempfile()
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
let app_o_file = app_o_file.path();
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
let builtins_host_tempfile = roc_bitcode::host_tempfile()
.expect("failed to write host builtins object to tempfile");
let mut inputs = vec![app_o_file.to_str().unwrap()];
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
// the host has been compiled into a .o or .obj file
inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
}
if matches!(code_gen_options.backend, CodeGenBackend::Assembly) {
inputs.push(builtins_host_tempfile.path().to_str().unwrap());
}
let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
let exit_status = child
.wait()
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
// Extend the lifetime of the tempfile so it doesn't get dropped
// (and thus deleted) before the child process is done using it!
let _ = builtins_host_tempfile;
if !exit_status.success() {
todo!(
"gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
exit_status.code()
);
}
}
}
let linking_time = link_start.elapsed();
if emit_timings {
println!("Finished linking in {} ms\n", linking_time.as_millis());
}
let total_time = compilation_start.elapsed();
Ok(BuiltFile {
binary_path: output_exe_path,
problems,
total_time,
expect_metadata,
})
}
fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
let prefix = match prebuilt_requested {
true => "Because I was run with --prebuilt-platform=true, ",
false => "",
};
let preprocessed_host_path_str = preprocessed_host_path.to_string_lossy();
let extra_err_msg = if preprocessed_host_path_str.ends_with(".rh") {
"\n\n\tNote: If the platform does have an .rh1 file but no .rh file, it's because it's been built with an older version of roc. Contact the author to release a new build of the platform using a roc release newer than March 21 2023.\n"
} else {
""
};
eprintln!(
indoc::indoc!(
r#"
{}I was expecting this file to exist:
{}
However, it was not there!{}
If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false
"#
),
prefix,
preprocessed_host_path.to_string_lossy(),
extra_err_msg
);
}
#[allow(clippy::too_many_arguments)]
fn spawn_rebuild_thread(
opt_level: OptLevel,
linking_strategy: LinkingStrategy,
platform_main_roc: PathBuf,
preprocessed_host_path: PathBuf,
output_exe_path: PathBuf,
target: &Triple,
dll_stub_symbols: Vec<String>,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Rebuilding platform...");
let rebuild_host_start = Instant::now();
match linking_strategy {
LinkingStrategy::Additive => {
let host_dest = rebuild_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
None,
);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
}
LinkingStrategy::Surgical => {
build_and_preprocess_host_lowlevel(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
preprocessed_host_path.as_path(),
&dll_stub_symbols,
);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
LinkingStrategy::Legacy => {
rebuild_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
None,
);
}
}
rebuild_host_start.elapsed().as_millis()
})
}
pub fn build_and_preprocess_host(
opt_level: OptLevel,
target: &Triple,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
exposed_symbols: roc_linker::ExposedSymbols,
) {
let stub_dll_symbols = exposed_symbols.stub_dll_symbols();
build_and_preprocess_host_lowlevel(
opt_level,
target,
platform_main_roc,
preprocessed_host_path,
&stub_dll_symbols,
)
}
fn build_and_preprocess_host_lowlevel(
opt_level: OptLevel,
target: &Triple,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
stub_dll_symbols: &[String],
) {
let stub_lib =
roc_linker::generate_stub_lib_from_loaded(target, platform_main_roc, stub_dll_symbols);
debug_assert!(stub_lib.exists());
rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib));
roc_linker::preprocess_host(
target,
platform_main_roc,
preprocessed_host_path,
&stub_lib,
stub_dll_symbols,
)
}
#[allow(clippy::too_many_arguments)]
pub fn check_file<'a>(
arena: &'a Bump,
roc_file_path: PathBuf,
emit_timings: bool,
roc_cache_dir: RocCacheDir<'_>,
threading: Threading,
) -> Result<(Problems, Duration), LoadingProblem<'a>> {
let compilation_start = Instant::now();
// only used for generating errors. We don't do code generation, so hardcoding should be fine
// we need monomorphization for when exhaustiveness checking
let target_info = TargetInfo::default_x86_64();
// Step 1: compile the app and generate the .o file
let load_config = LoadConfig {
target_info,
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
exec_mode: ExecutionMode::Check,
};
let mut loaded =
roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?;
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
report_timing(buf, "Parse header", module_timing.parse_header);
report_timing(buf, "Parse body", module_timing.parse_body);
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
if it.peek().is_some() {
buf.push('\n');
}
}
let compilation_end = compilation_start.elapsed();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
}
Ok((report_problems_typechecked(&mut loaded), compilation_end))
}
pub fn build_str_test<'a>(
arena: &'a Bump,
app_module_path: &Path,
app_module_source: &'a str,
assume_prebuild: bool,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let triple = target_lexicon::Triple::host();
let code_gen_options = CodeGenOptions {
backend: CodeGenBackend::Llvm(LlvmBackendMode::Binary),
opt_level: OptLevel::Normal,
emit_debug_info: false,
};
let emit_timings = false;
let link_type = LinkType::Executable;
let linking_strategy = LinkingStrategy::Surgical;
let wasm_dev_stack_bytes = None;
let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
let build_ordering = BuildOrdering::AlwaysBuild;
let threading = Threading::AtMost(2);
let load_config = standard_load_config(&triple, build_ordering, threading);
let compilation_start = std::time::Instant::now();
// Step 1: compile the app and generate the .o file
let loaded = roc_load::load_and_monomorphize_from_str(
arena,
PathBuf::from("valgrind_test.roc"),
app_module_source,
app_module_path.to_path_buf(),
roc_cache_dir,
load_config,
)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
build_loaded_file(
arena,
&triple,
app_module_path.to_path_buf(),
code_gen_options,
emit_timings,
link_type,
linking_strategy,
assume_prebuild,
wasm_dev_stack_bytes,
loaded,
compilation_start,
)
}

View file

@ -1,23 +1,17 @@
[package]
name = "roc_builtins"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides the Roc functions and modules that are implicitly imported into every module."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_target = { path = "../roc_target" }
roc_utils = { path = "../../utils" }
tempfile.workspace = true
[build-dependencies]
# dunce can be removed once ziglang/zig#5109 is fixed
dunce = "1.0.3"
roc_utils = { path = "../../utils" }
[target.'cfg(target_os = "macos")'.build-dependencies]
tempfile.workspace = true

View file

@ -0,0 +1,19 @@
[package]
name = "roc_bitcode"
description = "Compiles the zig bitcode to `.o` for builtins"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
tempfile.workspace = true
[build-dependencies]
roc_command_utils = { path = "../../../utils/command" }
# dunce can be removed once ziglang/zig#5109 is fixed
dunce = "1.0.3"
[target.'cfg(target_os = "macos")'.build-dependencies]
tempfile.workspace = true

View file

@ -0,0 +1,16 @@
[package]
name = "roc_bitcode_bc"
description = "Compiles the zig bitcode to `.bc` for llvm"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[build-dependencies]
roc_command_utils = { path = "../../../../utils/command" }
# dunce can be removed once ziglang/zig#5109 is fixed
dunce = "1.0.3"
[target.'cfg(target_os = "macos")'.build-dependencies]
tempfile.workspace = true

View file

@ -0,0 +1,154 @@
use roc_command_utils::{pretty_command_string, zig};
use std::fs;
use std::io;
use std::path::Path;
use std::str;
use std::{env, path::PathBuf, process::Command};
#[cfg(target_os = "macos")]
use tempfile::tempdir;
/// To debug the zig code with debug prints, we need to disable the wasm code gen
const DEBUG: bool = false;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// "." is relative to where "build.rs" is
// dunce can be removed once ziglang/zig#5109 is fixed
let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap().join("..");
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
let zig_cache_dir = tempdir().expect("Failed to create temp directory for zig cache");
#[cfg(target_os = "macos")]
std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
// LLVM .bc FILES
generate_bc_file(&bitcode_path, "ir", "builtins-host");
if !DEBUG {
generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
}
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
generate_bc_file(
&bitcode_path,
"ir-windows-x86_64",
"builtins-windows-x86_64",
);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path;
println!(
"cargo:rerun-if-changed={}",
path.to_str().expect("Failed to convert path to str")
);
})
.unwrap();
#[cfg(target_os = "macos")]
zig_cache_dir
.close()
.expect("Failed to delete temp dir zig_cache_dir.");
}
fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
let mut ll_path = bitcode_path.join(file_name);
ll_path.set_extension("ll");
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
println!("Compiling host ir to: {}", dest_ir_host);
let mut bc_path = bitcode_path.join(file_name);
bc_path.set_extension("bc");
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
let _ = fs::remove_dir_all("./zig-cache");
let mut zig_cmd = zig();
zig_cmd
.current_dir(bitcode_path)
.args(["build", zig_object, "-Drelease=true"]);
run_command(zig_cmd, 0);
}
pub fn get_lib_dir() -> PathBuf {
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
// So we just need to add "/bitcode" to that.
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
// create dir if it does not exist
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
dir
}
fn run_command(mut command: Command, flaky_fail_counter: usize) {
let command_str = pretty_command_string(&command);
let command_str = command_str.to_string_lossy();
let output_result = command.output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
// Flaky test errors that only occur sometimes on MacOS ci server.
if error_str.contains("FileNotFound")
|| error_str.contains("unable to save cached ZIR code")
|| error_str.contains("LLVM failed to emit asm")
{
if flaky_fail_counter == 10 {
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
} else {
run_command(command, flaky_fail_counter + 1)
}
} else if error_str
.contains("lld-link: error: failed to write the output file: Permission denied")
{
panic!("{} failed with:\n\n {}\n\nWorkaround:\n\n Re-run the cargo command that triggered this build.\n\n", command_str, error_str);
} else {
panic!("{} failed with:\n\n {}\n", command_str, error_str);
}
}
},
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path_buf = entry.path();
if path_buf.is_dir() {
if !path_buf.ends_with("zig-cache") {
get_zig_files(&path_buf, cb).unwrap();
}
} else {
let path = path_buf.as_path();
match path.extension() {
Some(osstr) if osstr == "zig" => {
cb(path);
}
_ => {}
}
}
}
}
Ok(())
}

View file

@ -0,0 +1 @@

View file

@ -1,11 +1,9 @@
use roc_utils::zig;
use std::env;
use roc_command_utils::{pretty_command_string, zig};
use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::str;
use std::{env, path::PathBuf, process::Command};
#[cfg(target_os = "macos")]
use tempfile::tempdir;
@ -18,8 +16,7 @@ fn main() {
// "." is relative to where "build.rs" is
// dunce can be removed once ziglang/zig#5109 is fixed
let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap();
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
@ -27,22 +24,6 @@ fn main() {
#[cfg(target_os = "macos")]
std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
// LLVM .bc FILES
generate_bc_file(&bitcode_path, "ir", "builtins-host");
if !DEBUG {
generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
}
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
generate_bc_file(
&bitcode_path,
"ir-windows-x86_64",
"builtins-windows-x86_64",
);
// OBJECT FILES
#[cfg(windows)]
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
@ -107,38 +88,13 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
}
}
fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
let mut ll_path = bitcode_path.join(file_name);
ll_path.set_extension("ll");
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
println!("Compiling host ir to: {}", dest_ir_host);
let mut bc_path = bitcode_path.join(file_name);
bc_path.set_extension("bc");
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
let _ = fs::remove_dir_all("./bitcode/zig-cache");
let mut zig_cmd = zig();
zig_cmd
.current_dir(bitcode_path)
.args(["build", zig_object, "-Drelease=true"]);
run_command(zig_cmd, 0);
}
pub fn get_lib_dir() -> PathBuf {
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
// So we just need to add "/bitcode" to that.
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bitcode");
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
// create dir if it does not exist
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/bitcode dir.");
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
dir
}
@ -192,7 +148,7 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
}
fn run_command(mut command: Command, flaky_fail_counter: usize) {
let command_str = roc_utils::pretty_command_string(&command);
let command_str = pretty_command_string(&command);
let command_str = command_str.to_string_lossy();
let output_result = command.output();

View file

@ -921,8 +921,8 @@ test "toStr: 123.1111111" {
test "toStr: 123.1111111111111 (big str)" {
var dec: RocDec = .{ .num = 123111111111111000000 };
var res_roc_str = dec.toStr();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
const res_slice: []const u8 = "123.111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@ -931,8 +931,8 @@ test "toStr: 123.1111111111111 (big str)" {
test "toStr: 123.111111111111444444 (max number of decimal places)" {
var dec: RocDec = .{ .num = 123111111111111444444 };
var res_roc_str = dec.toStr();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
const res_slice: []const u8 = "123.111111111111444444"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@ -941,8 +941,8 @@ test "toStr: 123.111111111111444444 (max number of decimal places)" {
test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
var dec: RocDec = .{ .num = 12345678912345678912111111111111111111 };
var res_roc_str = dec.toStr();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
const res_slice: []const u8 = "12345678912345678912.111111111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@ -951,8 +951,8 @@ test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
test "toStr: std.math.maxInt" {
var dec: RocDec = .{ .num = std.math.maxInt(i128) };
var res_roc_str = dec.toStr();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
const res_slice: []const u8 = "170141183460469231731.687303715884105727"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@ -961,8 +961,8 @@ test "toStr: std.math.maxInt" {
test "toStr: std.math.minInt" {
var dec: RocDec = .{ .num = std.math.minInt(i128) };
var res_roc_str = dec.toStr();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
errdefer res_roc_str.decref();
defer res_roc_str.decref();
const res_slice: []const u8 = "-170141183460469231731.687303715884105728"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@ -1047,8 +1047,8 @@ test "div: 10 / 3" {
var denom: RocDec = RocDec.fromU64(3);
var roc_str = RocStr.init("3.333333333333333333", 20);
errdefer roc_str.deinit();
defer roc_str.deinit();
errdefer roc_str.decref();
defer roc_str.decref();
var res: RocDec = RocDec.fromStr(roc_str).?;

View file

@ -0,0 +1,65 @@
use tempfile::NamedTempFile;
const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-wasm32.o"));
// TODO: in the future, we should use Zig's cross-compilation to generate and store these
// for all targets, so that we can do cross-compilation!
#[cfg(unix)]
const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-host.o"));
#[cfg(windows)]
const HOST_WINDOWS: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/builtins-windows-x86_64.obj"));
pub fn host_wasm_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".wasm")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WASM)?;
Ok(tempfile)
}
#[cfg(unix)]
fn host_unix_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".o")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_UNIX)?;
Ok(tempfile)
}
#[cfg(windows)]
fn host_windows_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".obj")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WINDOWS)?;
Ok(tempfile)
}
pub fn host_tempfile() -> std::io::Result<NamedTempFile> {
#[cfg(unix)]
{
host_unix_tempfile()
}
#[cfg(windows)]
{
host_windows_tempfile()
}
#[cfg(not(any(windows, unix)))]
{
unreachable!()
}
}

View file

@ -15,21 +15,45 @@ const IncN = fn (?[*]u8, usize) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
const HasTagId = fn (u16, ?[*]u8) callconv(.C) extern struct { matched: bool, data: ?[*]u8 };
const SEAMLESS_SLICE_BIT: usize =
@bitCast(usize, @as(isize, std.math.minInt(isize)));
pub const RocList = extern struct {
bytes: ?[*]u8,
length: usize,
capacity: usize,
// This technically points to directly after the refcount.
// This is an optimization that enables use one code path for regular lists and slices for geting the refcount ptr.
capacity_or_ref_ptr: usize,
pub inline fn len(self: RocList) usize {
return self.length;
}
pub fn getCapacity(self: RocList) usize {
const list_capacity = self.capacity_or_ref_ptr;
const slice_capacity = self.length;
const slice_mask = self.seamlessSliceMask();
const capacity = (list_capacity & ~slice_mask) | (slice_capacity & slice_mask);
return capacity;
}
pub fn isSeamlessSlice(self: RocList) bool {
return @bitCast(isize, self.capacity_or_ref_ptr) < 0;
}
// This returns all ones if the list is a seamless slice.
// Otherwise, it returns all zeros.
// This is done without branching for optimization purposes.
pub fn seamlessSliceMask(self: RocList) usize {
return @bitCast(usize, @bitCast(isize, self.capacity_or_ref_ptr) >> (@bitSizeOf(isize) - 1));
}
pub fn isEmpty(self: RocList) bool {
return self.len() == 0;
}
pub fn empty() RocList {
return RocList{ .bytes = null, .length = 0, .capacity = 0 };
return RocList{ .bytes = null, .length = 0, .capacity_or_ref_ptr = 0 };
}
pub fn eql(self: RocList, other: RocList) bool {
@ -75,8 +99,21 @@ pub const RocList = extern struct {
return list;
}
pub fn deinit(self: RocList, comptime T: type) void {
utils.decref(self.bytes, self.capacity, @alignOf(T));
// returns a pointer to just after the refcount.
// It is just after the refcount as an optimization for other shared code paths.
// For regular list, it just returns their bytes pointer.
// For seamless slices, it returns the pointer stored in capacity_or_ref_ptr.
pub fn getRefcountPtr(self: RocList) ?[*]u8 {
const list_ref_ptr = @ptrToInt(self.bytes);
const slice_ref_ptr = self.capacity_or_ref_ptr << 1;
const slice_mask = self.seamlessSliceMask();
const ref_ptr = (list_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask);
return @intToPtr(?[*]u8, ref_ptr);
}
pub fn decref(self: RocList, alignment: u32) void {
// We use the raw capacity to ensure we always decrement the refcount of seamless slices.
utils.decref(self.getRefcountPtr(), self.capacity_or_ref_ptr, alignment);
}
pub fn elements(self: RocList, comptime T: type) ?[*]T {
@ -88,7 +125,7 @@ pub const RocList = extern struct {
}
fn refcountMachine(self: RocList) usize {
if (self.capacity == 0) {
if (self.getCapacity() == 0 and !self.isSeamlessSlice()) {
// the zero-capacity is Clone, copying it will not leak memory
return utils.REFCOUNT_ONE;
}
@ -110,12 +147,15 @@ pub const RocList = extern struct {
}
pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList {
if (self.isEmpty()) {
if (self.isUnique()) {
return self;
}
if (self.isUnique()) {
return self;
if (self.isEmpty()) {
// Empty is not necessarily unique on it's own.
// The list could have capacity and be shared.
self.decref(alignment);
return RocList.empty();
}
// unfortunately, we have to clone
@ -127,9 +167,8 @@ pub const RocList = extern struct {
const number_of_bytes = self.len() * element_width;
@memcpy(new_bytes, old_bytes, number_of_bytes);
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.len() * element_width;
utils.decref(self.bytes, data_bytes, alignment);
// NOTE we fuse an increment of all keys/values with a decrement of the input list.
self.decref(alignment);
return new_list;
}
@ -148,7 +187,24 @@ pub const RocList = extern struct {
return RocList{
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
.length = length,
.capacity = capacity,
.capacity_or_ref_ptr = capacity,
};
}
pub fn allocateExact(
alignment: u32,
length: usize,
element_width: usize,
) RocList {
if (length == 0) {
return empty();
}
const data_bytes = length * element_width;
return RocList{
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
.length = length,
.capacity_or_ref_ptr = length,
};
}
@ -159,13 +215,14 @@ pub const RocList = extern struct {
element_width: usize,
) RocList {
if (self.bytes) |source_ptr| {
if (self.isUnique()) {
if (self.capacity >= new_length) {
return RocList{ .bytes = self.bytes, .length = new_length, .capacity = self.capacity };
if (self.isUnique() and !self.isSeamlessSlice()) {
const capacity = self.capacity_or_ref_ptr;
if (capacity >= new_length) {
return RocList{ .bytes = self.bytes, .length = new_length, .capacity_or_ref_ptr = capacity };
} else {
const new_capacity = utils.calculateCapacity(self.capacity, new_length, element_width);
const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_capacity, element_width);
return RocList{ .bytes = new_source, .length = new_length, .capacity = new_capacity };
const new_capacity = utils.calculateCapacity(capacity, new_length, element_width);
const new_source = utils.unsafeReallocate(source_ptr, alignment, capacity, new_capacity, element_width);
return RocList{ .bytes = new_source, .length = new_length, .capacity_or_ref_ptr = new_capacity };
}
}
return self.reallocateFresh(alignment, new_length, element_width);
@ -193,7 +250,7 @@ pub const RocList = extern struct {
@memset(dest_ptr + old_length * element_width, 0, delta_length * element_width);
}
utils.decref(self.bytes, old_length * element_width, alignment);
self.decref(alignment);
return result;
}
@ -428,7 +485,7 @@ pub fn listReserve(
update_mode: UpdateMode,
) callconv(.C) RocList {
const old_length = list.len();
if ((update_mode == .InPlace or list.isUnique()) and list.capacity >= list.len() + spare) {
if ((update_mode == .InPlace or list.isUnique()) and list.getCapacity() >= list.len() + spare) {
return list;
} else {
var output = list.reallocate(alignment, old_length + spare, element_width);
@ -437,6 +494,31 @@ pub fn listReserve(
}
}
pub fn listReleaseExcessCapacity(
list: RocList,
alignment: u32,
element_width: usize,
update_mode: UpdateMode,
) callconv(.C) RocList {
const old_length = list.len();
// We use the direct list.capacity_or_ref_ptr to make sure both that there is no extra capacity and that it isn't a seamless slice.
if ((update_mode == .InPlace or list.isUnique()) and list.capacity_or_ref_ptr == old_length) {
return list;
} else if (old_length == 0) {
list.decref(alignment);
return RocList.empty();
} else {
var output = RocList.allocateExact(alignment, old_length, element_width);
if (list.bytes) |source_ptr| {
const dest_ptr = output.bytes orelse unreachable;
@memcpy(dest_ptr, source_ptr, old_length * element_width);
}
list.decref(alignment);
return output;
}
}
pub fn listAppendUnsafe(
list: RocList,
element: Opaque,
@ -462,10 +544,12 @@ fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usi
pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList {
const old_length = list.len();
var output = list.reallocate(alignment, old_length + 1, element_width);
// TODO: properly wire in update mode.
var with_capacity = listReserve(list, alignment, 1, element_width, .Immutable);
with_capacity.length += 1;
// can't use one memcpy here because source and target overlap
if (output.bytes) |target| {
if (with_capacity.bytes) |target| {
var i: usize = old_length;
while (i > 0) {
@ -481,7 +565,7 @@ pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width
}
}
return output;
return with_capacity;
}
pub fn listSwap(
@ -522,19 +606,20 @@ pub fn listSublist(
) callconv(.C) RocList {
const size = list.len();
if (len == 0 or start >= size) {
if (list.isUnique()) {
// Decrement the reference counts of all elements.
if (list.bytes) |source_ptr| {
var i: usize = 0;
while (i < size) : (i += 1) {
const element = source_ptr + i * element_width;
dec(element);
}
var output = list;
output.length = 0;
return output;
// Decrement the reference counts of all elements.
if (list.bytes) |source_ptr| {
var i: usize = 0;
while (i < size) : (i += 1) {
const element = source_ptr + i * element_width;
dec(element);
}
}
if (list.isUnique()) {
var output = list;
output.length = 0;
return output;
}
list.decref(alignment);
return RocList.empty();
}
@ -557,26 +642,20 @@ pub fn listSublist(
dec(element);
}
if (list.isUnique()) {
if (start == 0 and list.isUnique()) {
var output = list;
output.length = keep_len;
if (start == 0) {
return output;
} else {
// We want memmove due to aliasing. Zig does not expose it directly.
// Instead use copy which can write to aliases as long as the dest is before the source.
mem.copy(u8, source_ptr[0 .. keep_len * element_width], source_ptr[start * element_width .. (start + keep_len) * element_width]);
return output;
}
} else {
const output = RocList.allocate(alignment, keep_len, element_width);
const target_ptr = output.bytes orelse unreachable;
@memcpy(target_ptr, source_ptr + start * element_width, keep_len * element_width);
utils.decref(list.bytes, size * element_width, alignment);
return output;
} else {
const list_ref_ptr = (@ptrToInt(source_ptr) >> 1) | SEAMLESS_SLICE_BIT;
const slice_ref_ptr = list.capacity_or_ref_ptr;
const slice_mask = list.seamlessSliceMask();
const ref_ptr = (list_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask);
return RocList{
.bytes = source_ptr + start * element_width,
.length = keep_len,
.capacity_or_ref_ptr = ref_ptr,
};
}
}
@ -590,9 +669,17 @@ pub fn listDropAt(
drop_index: usize,
dec: Dec,
) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
const size = list.len();
// If droping the first or last element, return a seamless slice.
// For simplicity, do this by calling listSublist.
// In the future, we can test if it is faster to manually inline the important parts here.
if (drop_index == 0) {
return listSublist(list, alignment, element_width, 1, size -| 1, dec);
} else if (drop_index == size -| 1) {
return listSublist(list, alignment, element_width, 0, size -| 1, dec);
}
if (list.bytes) |source_ptr| {
if (drop_index >= size) {
return list;
}
@ -607,7 +694,7 @@ pub fn listDropAt(
// because we rely on the pointer field being null if the list is empty
// which also requires duplicating the utils.decref call to spend the RC token
if (size < 2) {
utils.decref(list.bytes, size * element_width, alignment);
list.decref(alignment);
return RocList.empty();
}
@ -637,7 +724,7 @@ pub fn listDropAt(
const tail_size = (size - drop_index - 1) * element_width;
@memcpy(tail_target, tail_source, tail_size);
utils.decref(list.bytes, size * element_width, alignment);
list.decref(alignment);
return output;
} else {
@ -747,11 +834,13 @@ fn swapElements(source_ptr: [*]u8, element_width: usize, index_1: usize, index_2
pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_width: usize) callconv(.C) RocList {
// NOTE we always use list_a! because it is owned, we must consume it, and it may have unused capacity
if (list_b.isEmpty()) {
if (list_a.capacity == 0) {
if (list_a.getCapacity() == 0) {
// a could be a seamless slice, so we still need to decref.
list_a.decref(alignment);
return list_b;
} else {
// we must consume this list. Even though it has no elements, it could still have capacity
list_b.deinit(usize);
list_b.decref(alignment);
return list_a;
}
@ -766,7 +855,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
@memcpy(source_a + list_a.len() * element_width, source_b, list_b.len() * element_width);
// decrement list b.
utils.decref(source_b, list_b.len(), alignment);
list_b.decref(alignment);
return resized_list_a;
} else if (list_b.isUnique()) {
@ -787,7 +876,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
@memcpy(source_b, source_a, byte_count_a);
// decrement list a.
utils.decref(source_a, list_a.len(), alignment);
list_a.decref(alignment);
return resized_list_b;
}
@ -804,8 +893,8 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
@memcpy(target + list_a.len() * element_width, source_b, list_b.len() * element_width);
// decrement list a and b.
utils.decref(source_a, list_a.len(), alignment);
utils.decref(source_b, list_b.len(), alignment);
list_a.decref(alignment);
list_b.decref(alignment);
return output;
}
@ -868,20 +957,32 @@ pub fn listIsUnique(
return list.isEmpty() or list.isUnique();
}
pub fn listCapacity(
list: RocList,
) callconv(.C) usize {
return list.getCapacity();
}
pub fn listRefcountPtr(
list: RocList,
) callconv(.C) ?[*]u8 {
return list.getRefcountPtr();
}
test "listConcat: non-unique with unique overlapping" {
var nonUnique = RocList.fromSlice(u8, ([_]u8{1})[0..]);
var bytes: [*]u8 = @ptrCast([*]u8, nonUnique.bytes);
const ptr_width = @sizeOf(usize);
const refcount_ptr = @ptrCast([*]isize, @alignCast(ptr_width, bytes) - ptr_width);
utils.increfC(&refcount_ptr[0], 1);
defer nonUnique.deinit(u8); // listConcat will dec the other refcount
defer nonUnique.decref(@sizeOf(u8)); // listConcat will dec the other refcount
var unique = RocList.fromSlice(u8, ([_]u8{ 2, 3, 4 })[0..]);
defer unique.deinit(u8);
defer unique.decref(@sizeOf(u8));
var concatted = listConcat(nonUnique, unique, 1, 1);
var wanted = RocList.fromSlice(u8, ([_]u8{ 1, 2, 3, 4 })[0..]);
defer wanted.deinit(u8);
defer wanted.decref(@sizeOf(u8));
try expect(concatted.eql(wanted));
}

View file

@ -54,6 +54,9 @@ comptime {
exportListFn(list.listReplaceInPlace, "replace_in_place");
exportListFn(list.listSwap, "swap");
exportListFn(list.listIsUnique, "is_unique");
exportListFn(list.listCapacity, "capacity");
exportListFn(list.listRefcountPtr, "refcount_ptr");
exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity");
}
// Num Module
@ -67,6 +70,8 @@ const NUMBERS = INTEGERS ++ FLOATS;
comptime {
exportNumFn(num.bytesToU16C, "bytes_to_u16");
exportNumFn(num.bytesToU32C, "bytes_to_u32");
exportNumFn(num.bytesToU64C, "bytes_to_u64");
exportNumFn(num.bytesToU128C, "bytes_to_u128");
inline for (INTEGERS) |T, i| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
@ -86,6 +91,10 @@ comptime {
num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow.");
num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic.");
num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated.");
num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits.");
num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits.");
num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits.");
}
inline for (INTEGERS) |FROM| {
@ -138,7 +147,6 @@ comptime {
exportStrFn(str.getScalarUnsafe, "get_scalar_unsafe");
exportStrFn(str.appendScalar, "append_scalar");
exportStrFn(str.strToUtf8C, "to_utf8");
exportStrFn(str.fromUtf8C, "from_utf8");
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim");
@ -147,6 +155,8 @@ comptime {
exportStrFn(str.strCloneTo, "clone_to");
exportStrFn(str.withCapacity, "with_capacity");
exportStrFn(str.strGraphemes, "graphemes");
exportStrFn(str.strRefcountPtr, "refcount_ptr");
exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
inline for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");

View file

@ -236,6 +236,24 @@ fn bytesToU32(arg: RocList, position: usize) u32 {
return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] });
}
pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 {
return @call(.{ .modifier = always_inline }, bytesToU64, .{ arg, position });
}
fn bytesToU64(arg: RocList, position: usize) u64 {
const bytes = @ptrCast([*]const u8, arg.bytes);
return @bitCast(u64, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7] });
}
pub fn bytesToU128C(arg: RocList, position: usize) callconv(.C) u128 {
return @call(.{ .modifier = always_inline }, bytesToU128, .{ arg, position });
}
fn bytesToU128(arg: RocList, position: usize) u128 {
const bytes = @ptrCast([*]const u8, arg.bytes);
return @bitCast(u128, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] });
}
fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
switch (@typeInfo(T)) {
.Int => {
@ -460,3 +478,30 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCountLeadingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @clz(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCountTrailingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @ctz(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @popCount(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}

File diff suppressed because it is too large Load diff

View file

@ -1,64 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
mod bitcode {
use roc_builtins_bitcode::{count_segments_, str_split_};
#[test]
fn count_segments() {
assert_eq!(
count_segments_((&"hello there").as_bytes(), (&"hello").as_bytes()),
2
);
assert_eq!(
count_segments_((&"a\nb\nc").as_bytes(), (&"\n").as_bytes()),
3
);
assert_eq!(
count_segments_((&"str").as_bytes(), (&"delimiter").as_bytes()),
1
);
}
#[test]
fn str_split() {
fn splits_to(string: &str, delimiter: &str, expectation: &[&[u8]]) {
assert_eq!(
str_split_(
&mut [(&"").as_bytes()].repeat(expectation.len()),
&string.as_bytes(),
&delimiter.as_bytes()
),
expectation
);
}
splits_to(
"a!b!c",
"!",
&[(&"a").as_bytes(), (&"b").as_bytes(), (&"c").as_bytes()],
);
splits_to(
"a!?b!?c!?",
"!?",
&[
(&"a").as_bytes(),
(&"b").as_bytes(),
(&"c").as_bytes(),
(&"").as_bytes(),
],
);
splits_to("abc", "!", &[(&"abc").as_bytes()]);
splits_to(
"tttttghittttt",
"ttttt",
&[(&"").as_bytes(), (&"ghi").as_bytes(), (&"").as_bytes()],
);
splits_to("def", "!!!!!!", &[(&"def").as_bytes()]);
}
}

View file

@ -32,6 +32,8 @@ Eq has
## cannot derive `isEq` for types that contain functions.
isEq : a, a -> Bool | a has Eq
## Represents the boolean true and false using an opaque type.
## `Bool` implements the `Eq` ability.
Bool := [True, False] has [Eq { isEq: boolIsEq }]
boolIsEq = \@Bool b1, @Bool b2 -> structuralEq b1 b2
@ -49,23 +51,27 @@ false = @Bool False
## gate. The infix operator `&&` can also be used as shorthand for
## `Bool.and`.
##
## expect (Bool.and Bool.true Bool.true) == Bool.true
## expect (Bool.true && Bool.true) == Bool.true
## expect (Bool.false && Bool.true) == Bool.false
## expect (Bool.true && Bool.false) == Bool.false
## expect (Bool.false && Bool.false) == Bool.false
## ```
## expect (Bool.and Bool.true Bool.true) == Bool.true
## expect (Bool.true && Bool.true) == Bool.true
## expect (Bool.false && Bool.true) == Bool.false
## expect (Bool.true && Bool.false) == Bool.false
## expect (Bool.false && Bool.false) == Bool.false
## ```
##
## **Performance Note** that in Roc the `&&` and `||` work the same way as any
## ## Performance Details
##
## In Roc the `&&` and `||` work the same way as any
## other function. However, in some languages `&&` and `||` are special-cased.
## In these languages the compiler will skip evaluating the expression after the
## first operator under certain circumstances. For example an expression like
## `enablePets && likesDogs user` would compile to.
##
## if enablePets then
## likesDogs user
## else
## Bool.false
##
## ```
## if enablePets then
## likesDogs user
## else
## Bool.false
## ```
## Roc does not do this because conditionals like `if` and `when` have a
## performance cost. Calling a function can sometimes be faster across the board
## than doing an `if` to decide whether to skip calling it.
@ -74,14 +80,17 @@ and : Bool, Bool -> Bool
## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
## The infix operator `||` can also be used as shorthand for `Bool.or`.
## ```
## expect (Bool.or Bool.false Bool.true) == Bool.true
## expect (Bool.true || Bool.true) == Bool.true
## expect (Bool.false || Bool.true) == Bool.true
## expect (Bool.true || Bool.false) == Bool.true
## expect (Bool.false || Bool.false) == Bool.false
## ```
##
## expect (Bool.or Bool.false Bool.true) == Bool.true
## expect (Bool.true || Bool.true) == Bool.true
## expect (Bool.false || Bool.true) == Bool.true
## expect (Bool.true || Bool.false) == Bool.true
## expect (Bool.false || Bool.false) == Bool.false
## ## Performance Details
##
## **Performance Note** that in Roc the `&&` and `||` work the same way as any
## In Roc the `&&` and `||` work the same way as any
## other functions. However, in some languages `&&` and `||` are special-cased.
## Refer to the note in `Bool.and` for more detail.
or : Bool, Bool -> Bool
@ -89,9 +98,10 @@ or : Bool, Bool -> Bool
## Returns `Bool.false` when given `Bool.true`, and vice versa. This is
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
## gate. The operator `!` can also be used as shorthand for `Bool.not`.
##
## expect (Bool.not Bool.false) == Bool.true
## expect (!Bool.false) == Bool.true
## ```
## expect (Bool.not Bool.false) == Bool.true
## expect (!Bool.false) == Bool.true
## ```
not : Bool -> Bool
## This will call the function `Bool.isEq` on the inputs, and then `Bool.not`
@ -101,10 +111,11 @@ not : Bool -> Bool
##
## **Note** that `isNotEq` does not accept arguments whose types contain
## functions.
##
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
## expect (Bool.false != Bool.false) == Bool.false
## expect "Apples" != "Oranges"
## ```
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
## expect (Bool.false != Bool.false) == Bool.false
## expect "Apples" != "Oranges"
## ```
isNotEq : a, a -> Bool | a has Eq
isNotEq = \a, b -> structuralNotEq a b

View file

@ -6,13 +6,15 @@ interface Box
## the value from the stack to the heap. This may provide a performance
## optimization for advanced use cases with large values. A platform may require
## that some values are boxed.
##
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## ```
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## ```
box : a -> Box a
## Returns a boxed value.
##
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## ```
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## ```
unbox : Box a -> a
# # we'd need reset/reuse for box for this to be efficient

View file

@ -23,6 +23,7 @@ interface Decode
string,
list,
record,
tuple,
custom,
decodeWith,
fromBytesPartial,
@ -43,6 +44,7 @@ interface Decode
I32,
I64,
I128,
Nat,
F32,
F64,
Dec,
@ -76,8 +78,24 @@ DecoderFormatting has
bool : Decoder Bool fmt | fmt has DecoderFormatting
string : Decoder Str fmt | fmt has DecoderFormatting
list : Decoder elem fmt -> Decoder (List elem) fmt | fmt has DecoderFormatting
## `record state stepField finalizer` decodes a record field-by-field.
##
## `stepField` returns a decoder for the given field in the record, or
## `Skip` if the field is not a part of the decoded record.
##
## `finalizer` should produce the record value from the decoded `state`.
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting
## `tuple state stepElem finalizer` decodes a tuple element-by-element.
##
## `stepElem` returns a decoder for the nth index in the tuple, or
## `TooLong` if the index is larger than the expected size of the tuple. The
## index passed to `stepElem` is 0-indexed.
##
## `finalizer` should produce the tuple value from the decoded `state`.
tuple : state, (state, Nat -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting
custom : (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
custom = \decode -> @Decoder decode

View file

@ -34,7 +34,7 @@ interface Dict
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you
## associate keys with values.
##
## ### Inserting
## ## Inserting
##
## The most basic way to use a dictionary is to start with an empty one and
## then:
@ -45,16 +45,16 @@ interface Dict
##
## Here's an example of a dictionary which uses a city's name as the key, and
## its population as the associated value.
##
## populationByCity =
## Dict.empty {}
## |> Dict.insert "London" 8_961_989
## |> Dict.insert "Philadelphia" 1_603_797
## |> Dict.insert "Shanghai" 24_870_895
## |> Dict.insert "Delhi" 16_787_941
## |> Dict.insert "Amsterdam" 872_680
##
## ### Accessing keys or values
## ```
## populationByCity =
## Dict.empty {}
## |> Dict.insert "London" 8_961_989
## |> Dict.insert "Philadelphia" 1_603_797
## |> Dict.insert "Shanghai" 24_870_895
## |> Dict.insert "Delhi" 16_787_941
## |> Dict.insert "Amsterdam" 872_680
## ```
## ## Accessing keys or values
##
## We can use [Dict.keys] and [Dict.values] functions to get only the keys or
## only the values.
@ -63,16 +63,16 @@ interface Dict
## order. This will be true if all you ever do is [Dict.insert] and [Dict.get] operations
## on the dictionary, but [Dict.remove] operations can change this order.
##
## ### Removing
## ## Removing
##
## We can remove an element from the dictionary, like so:
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.keys
## ==
## ["London", "Amsterdam", "Shanghai", "Delhi"]
##
## ```
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.keys
## ==
## ["London", "Amsterdam", "Shanghai", "Delhi"]
## ```
## Notice that the order has changed. Philadelphia was not only removed from the
## list, but Amsterdam - the last entry we inserted - has been moved into the
## spot where Philadelphia was previously. This is exactly what [Dict.remove]
@ -100,6 +100,9 @@ Dict k v := {
} | k has Hash & Eq
## Return an empty dictionary.
## ```
## emptyDict = Dict.empty {}
## ```
empty : {} -> Dict k v | k has Hash & Eq
empty = \{} ->
@Dict {
@ -110,6 +113,13 @@ empty = \{} ->
}
## Returns the max number of elements the dictionary can hold before requiring a rehash.
## ```
## foodDict =
## Dict.empty {}
## |> Dict.insert "apple" "fruit"
##
## capacityOfDict = Dict.capacity foodDict
## ```
capacity : Dict k v -> Nat | k has Hash & Eq
capacity = \@Dict { dataIndices } ->
cap = List.len dataIndices
@ -125,41 +135,55 @@ withCapacity = \_ ->
empty {}
## Returns a dictionary containing the key and value provided as input.
##
## expect
## Dict.single "A" "B"
## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
## ```
## expect
## Dict.single "A" "B"
## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
## ```
single : k, v -> Dict k v | k has Hash & Eq
single = \k, v ->
insert (empty {}) k v
## Returns dictionary with the keys and values specified by the input [List].
##
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"])
## ```
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"])
## ```
fromList : List (T k v) -> Dict k v | k has Hash & Eq
fromList = \data ->
# TODO: make this efficient. Should just set data and then set all indicies in the hashmap.
List.walk data (empty {}) (\dict, T k v -> insert dict k v)
## Returns the number of values in the dictionary.
##
## expect
## Dict.empty {}
## |> Dict.insert "One" "A Song"
## |> Dict.insert "Two" "Candy Canes"
## |> Dict.insert "Three" "Boughs of Holly"
## |> Dict.len
## |> Bool.isEq 3
## ```
## expect
## Dict.empty {}
## |> Dict.insert "One" "A Song"
## |> Dict.insert "Two" "Candy Canes"
## |> Dict.insert "Three" "Boughs of Holly"
## |> Dict.len
## |> Bool.isEq 3
## ```
len : Dict k v -> Nat | k has Hash & Eq
len = \@Dict { size } ->
size
## Clears all elements from a dictionary keeping around the allocation if it isn't huge.
## ```
## songs =
## Dict.empty {}
## |> Dict.insert "One" "A Song"
## |> Dict.insert "Two" "Candy Canes"
## |> Dict.insert "Three" "Boughs of Holly"
##
## clearSongs = Dict.clear songs
##
## expect Dict.len clearSongs == 0
## ```
clear : Dict k v -> Dict k v | k has Hash & Eq
clear = \@Dict { metadata, dataIndices, data } ->
cap = List.len dataIndices
@ -180,13 +204,14 @@ clear = \@Dict { metadata, dataIndices, data } ->
## Iterate through the keys and values in the dictionary and call the provided
## function with signature `state, k, v -> state` for each value, with an
## initial `state` value provided for the first call.
##
## expect
## Dict.empty {}
## |> Dict.insert "Apples" 12
## |> Dict.insert "Orange" 24
## |> Dict.walk 0 (\count, _, qty -> count + qty)
## |> Bool.isEq 36
## ```
## expect
## Dict.empty {}
## |> Dict.insert "Apples" 12
## |> Dict.insert "Orange" 24
## |> Dict.walk 0 (\count, _, qty -> count + qty)
## |> Bool.isEq 36
## ```
walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq
walk = \@Dict { data }, initialState, transform ->
List.walk data initialState (\state, T k v -> transform state k v)
@ -202,20 +227,38 @@ walk = \@Dict { data }, initialState, transform ->
##
## As such, it is typically better for performance to use this over [Dict.walk]
## if returning `Break` earlier than the last element is expected to be common.
## ```
## people =
## Dict.empty {}
## |> Dict.insert "Alice" 17
## |> Dict.insert "Bob" 18
## |> Dict.insert "Charlie" 19
##
## isAdult = \_, _, age ->
## if age >= 18 then
## Break Bool.true
## else
## Continue Bool.false
##
## someoneIsAnAdult = Dict.walkUntil people Bool.false isAdult
##
## expect someoneIsAnAdult == Bool.true
## ```
walkUntil : Dict k v, state, (state, k, v -> [Continue state, Break state]) -> state | k has Hash & Eq
walkUntil = \@Dict { data }, initialState, transform ->
List.walkUntil data initialState (\state, T k v -> transform state k v)
## Get the value for a given key. If there is a value for the specified key it
## will return [Ok value], otherwise return [Err KeyNotFound].
## ```
## dictionary =
## Dict.empty {}
## |> Dict.insert 1 "Apple"
## |> Dict.insert 2 "Orange"
##
## dictionary =
## Dict.empty {}
## |> Dict.insert 1 "Apple"
## |> Dict.insert 2 "Orange"
##
## expect Dict.get dictionary 1 == Ok "Apple"
## expect Dict.get dictionary 2000 == Err KeyNotFound
## expect Dict.get dictionary 1 == Ok "Apple"
## expect Dict.get dictionary 2000 == Err KeyNotFound
## ```
get : Dict k v, k -> Result v [KeyNotFound] | k has Hash & Eq
get = \@Dict { metadata, dataIndices, data }, key ->
hashKey =
@ -237,12 +280,13 @@ get = \@Dict { metadata, dataIndices, data }, key ->
Err KeyNotFound
## Check if the dictionary has a value for a specified key.
##
## expect
## Dict.empty {}
## |> Dict.insert 1234 "5678"
## |> Dict.contains 1234
## |> Bool.isEq Bool.true
## ```
## expect
## Dict.empty {}
## |> Dict.insert 1234 "5678"
## |> Dict.contains 1234
## |> Bool.isEq Bool.true
## ```
contains : Dict k v, k -> Bool | k has Hash & Eq
contains = \@Dict { metadata, dataIndices, data }, key ->
hashKey =
@ -261,12 +305,13 @@ contains = \@Dict { metadata, dataIndices, data }, key ->
Bool.false
## Insert a value into the dictionary at a specified key.
##
## expect
## Dict.empty {}
## |> Dict.insert "Apples" 12
## |> Dict.get "Apples"
## |> Bool.isEq (Ok 12)
## ```
## expect
## Dict.empty {}
## |> Dict.insert "Apples" 12
## |> Dict.get "Apples"
## |> Bool.isEq (Ok 12)
## ```
insert : Dict k v, k, v -> Dict k v | k has Hash & Eq
insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
hashKey =
@ -305,13 +350,14 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
insertNotFoundHelper rehashedDict key value h1Key h2Key
## Remove a value from the dictionary for a specified key.
##
## expect
## Dict.empty {}
## |> Dict.insert "Some" "Value"
## |> Dict.remove "Some"
## |> Dict.len
## |> Bool.isEq 0
## ```
## expect
## Dict.empty {}
## |> Dict.insert "Some" "Value"
## |> Dict.remove "Some"
## |> Dict.len
## |> Bool.isEq 0
## ```
remove : Dict k v, k -> Dict k v | k has Hash & Eq
remove = \@Dict { metadata, dataIndices, data, size }, key ->
# TODO: change this from swap remove to tombstone and test is performance is still good.
@ -345,16 +391,17 @@ remove = \@Dict { metadata, dataIndices, data, size }, key ->
## performance optimisation for the use case of providing a default when a value
## is missing. This is more efficient than doing both a `Dict.get` and then a
## `Dict.insert` call, and supports being piped.
## ```
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
## alterValue = \possibleValue ->
## when possibleValue is
## Missing -> Present Bool.false
## Present value -> if value then Missing else Present Bool.true
##
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
## alterValue = \possibleValue ->
## when possibleValue is
## Missing -> Present Bool.false
## Present value -> if value then Missing else Present Bool.true
##
## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
## ```
update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v | k has Hash & Eq
update = \dict, key, alter ->
# TODO: look into optimizing by merging substeps and reducing lookups.
@ -369,42 +416,45 @@ update = \dict, key, alter ->
## Returns the keys and values of a dictionary as a [List].
## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead.
##
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Dict.toList
## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]
## ```
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Dict.toList
## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]
## ```
toList : Dict k v -> List (T k v) | k has Hash & Eq
toList = \@Dict { data } ->
data
## Returns the keys of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
##
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Dict.keys
## |> Bool.isEq [1,2,3,4]
## ```
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Dict.keys
## |> Bool.isEq [1,2,3,4]
## ```
keys : Dict k v -> List k | k has Hash & Eq
keys = \@Dict { data } ->
List.map data (\T k _ -> k)
## Returns the values of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
##
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Dict.values
## |> Bool.isEq ["One","Two","Three","Four"]
## ```
## expect
## Dict.single 1 "One"
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Dict.values
## |> Bool.isEq ["One","Two","Three","Four"]
## ```
values : Dict k v -> List v | k has Hash & Eq
values = \@Dict { data } ->
List.map data (\T _ v -> v)
@ -414,24 +464,25 @@ values = \@Dict { data } ->
## both dictionaries will be combined. Note that where there are pairs
## with the same key, the value contained in the second input will be
## retained, and the value in the first input will be removed.
## ```
## first =
## Dict.single 1 "Not Me"
## |> Dict.insert 2 "And Me"
##
## first =
## Dict.single 1 "Not Me"
## |> Dict.insert 2 "And Me"
## second =
## Dict.single 1 "Keep Me"
## |> Dict.insert 3 "Me Too"
## |> Dict.insert 4 "And Also Me"
##
## second =
## Dict.single 1 "Keep Me"
## |> Dict.insert 3 "Me Too"
## |> Dict.insert 4 "And Also Me"
## expected =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "Me Too"
## |> Dict.insert 4 "And Also Me"
##
## expected =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "Me Too"
## |> Dict.insert 4 "And Also Me"
##
## expect
## Dict.insertAll first second == expected
## expect
## Dict.insertAll first second == expected
## ```
insertAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
insertAll = \xs, ys ->
walk ys xs insert
@ -441,18 +492,19 @@ insertAll = \xs, ys ->
## that are in both dictionaries. Note that where there are pairs with
## the same key, the value contained in the first input will be retained,
## and the value in the second input will be removed.
## ```
## first =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
##
## first =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## second =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "But Not Me"
## |> Dict.insert 4 "Or Me"
##
## second =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "But Not Me"
## |> Dict.insert 4 "Or Me"
##
## expect Dict.keepShared first second == first
## expect Dict.keepShared first second == first
## ```
keepShared : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
keepShared = \xs, ys ->
walk
@ -469,21 +521,22 @@ keepShared = \xs, ys ->
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
## of the values. This means that we will be left with only those pairs that
## are in the first dictionary and whose keys are not in the second.
## ```
## first =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "Remove Me"
##
## first =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "Remove Me"
## second =
## Dict.single 3 "Remove Me"
## |> Dict.insert 4 "I do nothing..."
##
## second =
## Dict.single 3 "Remove Me"
## |> Dict.insert 4 "I do nothing..."
## expected =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
##
## expected =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
##
## expect Dict.removeAll first second == expected
## expect Dict.removeAll first second == expected
## ```
removeAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
removeAll = \xs, ys ->
walk ys xs (\state, k, _ -> remove state k)
@ -851,7 +904,7 @@ LowLevelHasher := { originalSeed : U64, state : U64 } has [
# TODO hide behind an InternalList.roc module
listGetUnsafe : List a, Nat -> a
createLowLevelHasher : { seed ?U64 } -> LowLevelHasher
createLowLevelHasher : { seed ? U64 } -> LowLevelHasher
createLowLevelHasher = \{ seed ? 0x526F_6352_616E_643F } ->
@LowLevelHasher { originalSeed: seed, state: seed }
@ -1220,3 +1273,25 @@ expect
|> complete
hash1 != hash2
expect
empty {}
|> len
|> Bool.isEq 0
expect
empty {}
|> insert "One" "A Song"
|> insert "Two" "Candy Canes"
|> insert "Three" "Boughs of Holly"
|> clear
|> len
|> Bool.isEq 0
expect
Dict.empty {}
|> Dict.insert "Alice" 17
|> Dict.insert "Bob" 18
|> Dict.insert "Charlie" 19
|> Dict.walkUntil Bool.false (\_, _, age -> if age >= 18 then Break Bool.true else Continue Bool.false)
|> Bool.isEq Bool.true

View file

@ -22,6 +22,7 @@ interface Encode
list,
record,
tag,
tuple,
custom,
appendWith,
append,
@ -69,6 +70,7 @@ EncoderFormatting has
string : Str -> Encoder fmt | fmt has EncoderFormatting
list : List elem, (elem -> Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
record : List { key : Str, value : Encoder fmt } -> Encoder fmt | fmt has EncoderFormatting
tuple : List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
tag : Str, List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting

View file

@ -9,6 +9,7 @@ interface Hash
addU32,
addU64,
addU128,
hashBool,
hashI8,
hashI16,
hashI32,
@ -20,7 +21,7 @@ interface Hash
hashList,
hashUnordered,
] imports [
Bool.{ isEq },
Bool.{ Bool, isEq },
List,
Str,
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Nat },
@ -70,6 +71,12 @@ hashList = \hasher, lst ->
List.walk lst hasher \accumHasher, elem ->
hash accumHasher elem
## Adds a single [Bool] to a hasher.
hashBool : a, Bool -> a | a has Hasher
hashBool = \hasher, b ->
asU8 = if b then 1 else 0
addU8 hasher asU8
## Adds a single I8 to a hasher.
hashI8 : a, I8 -> a | a has Hasher
hashI8 = \hasher, n -> addU8 hasher (Num.toU8 n)

View file

@ -1,3 +1,40 @@
## JSON is a data format that is easy for humans to read and write. It is
## commonly used to exhange data between two systems such as a server and a
## client (e.g. web browser).
##
## This module implements functionality to serialise and de-serialise Roc types
## to and from JSON data. Using the `Encode` and `Decode` builtins this process
## can be achieved without the need to write custom encoder and decoder functions
## to parse UTF-8 strings.
##
## Here is a basic example which shows how to parse a JSON record into a Roc
## type named `Language` which includes a `name` field. The JSON string is
## decoded and then the field is encoded back into a UTF-8 string.
##
## ```
## Language : {
## name : Str,
## }
##
## jsonStr = Str.toUtf8 "{\"name\":\"Röc Lang\"}"
##
## result : Result Language _
## result =
## jsonStr
## |> Decode.fromBytes fromUtf8 # returns `Ok {name : "Röc Lang"}`
##
## name =
## decodedValue <- Result.map result
##
## Encode.toBytes decodedValue.name toUtf8
##
## expect name == Ok (Str.toUtf8 "\"Röc Lang\"")
## ```
##
## **Note:** This module is likely to be moved out of the builtins in future.
## It is currently located here to facilitate development of the Abilities
## language feature and testing. You are welcome to use this module, just note
## that it will be moved into a package in a future update.
interface Json
exposes [
Json,
@ -7,6 +44,7 @@ interface Json
imports [
List,
Str,
Result.{ Result },
Encode,
Encode.{
Encoder,
@ -37,6 +75,8 @@ interface Json
Result,
]
## An opaque type with the `EncoderFormatting` and
## `DecoderFormatting` abilities.
Json := {} has [
EncoderFormatting {
u8: encodeU8,
@ -56,6 +96,7 @@ Json := {} has [
string: encodeString,
list: encodeList,
record: encodeRecord,
tuple: encodeTuple,
tag: encodeTag,
},
DecoderFormatting {
@ -76,11 +117,14 @@ Json := {} has [
string: decodeString,
list: decodeList,
record: decodeRecord,
tuple: decodeTuple,
},
]
## Returns a JSON `Decoder`
toUtf8 = @Json {}
## Returns a JSON `Encoder`
fromUtf8 = @Json {}
numToBytes = \n ->
@ -165,6 +209,25 @@ encodeRecord = \fields ->
List.append bytesWithRecord (Num.toU8 '}')
encodeTuple = \elems ->
Encode.custom \bytes, @Json {} ->
writeTuple = \{ buffer, elemsLeft }, elemEncoder ->
bufferWithElem =
appendWith buffer elemEncoder (@Json {})
bufferWithSuffix =
if elemsLeft > 1 then
List.append bufferWithElem (Num.toU8 ',')
else
bufferWithElem
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
bytesHead = List.append bytes (Num.toU8 '[')
{ buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple
List.append bytesWithRecord (Num.toU8 ']')
encodeTag = \name, payload ->
Encode.custom \bytes, @Json {} ->
# Idea: encode `A v1 v2` as `{"A": [v1, v2]}`
@ -191,16 +254,42 @@ encodeTag = \name, payload ->
List.append bytesWithPayload (Num.toU8 ']')
|> List.append (Num.toU8 '}')
isEscapeSequence : U8, U8 -> Bool
isEscapeSequence = \a, b ->
when P a b is
P '\\' 'b' -> Bool.true # Backspace
P '\\' 'f' -> Bool.true # Form feed
P '\\' 'n' -> Bool.true # Newline
P '\\' 'r' -> Bool.true # Carriage return
P '\\' 't' -> Bool.true # Tab
P '\\' '"' -> Bool.true # Double quote
P '\\' '\\' -> Bool.true # Backslash
_ -> Bool.false
takeWhile = \list, predicate ->
helper = \{ taken, rest } ->
when List.first rest is
Ok elem ->
if predicate elem then
helper { taken: List.append taken elem, rest: List.split rest 1 |> .others }
when rest is
[a, b, ..] ->
if isEscapeSequence a b then
helper {
taken: taken |> List.append a |> List.append b,
rest: List.drop rest 2,
}
else if predicate a then
helper {
taken: List.append taken a,
rest: List.dropFirst rest,
}
else
{ taken, rest }
Err _ -> { taken, rest }
[a, ..] if predicate a ->
helper {
taken: List.append taken a,
rest: List.dropFirst rest,
}
_ -> { taken, rest }
helper { taken: [], rest: list }
@ -341,7 +430,6 @@ jsonString = \bytes ->
if
before == ['"']
then
# TODO: handle escape sequences
{ taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"'
when Str.fromUtf8 strSequence is
@ -358,42 +446,38 @@ decodeString = Decode.custom \bytes, @Json {} ->
jsonString bytes
decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
decodeElems = \chunk, accum ->
when Decode.decodeWith chunk decodeElem (@Json {}) is
{ result, rest } ->
when result is
Ok val ->
# TODO: handle spaces before ','
{ before: afterElem, others } = List.split rest 1
if
afterElem == [',']
then
decodeElems others (List.append accum val)
else
Done (List.append accum val) rest
Err e -> Errored e rest
Ok val ->
restWithoutWhitespace = eatWhitespace rest
when restWithoutWhitespace is
[',', ..] -> decodeElems (eatWhitespace (List.dropFirst restWithoutWhitespace)) (List.append accum val)
_ -> Done (List.append accum val) restWithoutWhitespace
{ before, others: afterStartingBrace } = List.split bytes 1
when bytes is
['[', ']'] -> { result: Ok [], rest: List.drop bytes 2 }
['[', ..] ->
bytesWithoutWhitespace = eatWhitespace (List.dropFirst bytes)
when bytesWithoutWhitespace is
[']', ..] ->
{ result: Ok [], rest: List.dropFirst bytesWithoutWhitespace }
if
before == ['[']
then
# TODO: empty lists
when decodeElems afterStartingBrace [] is
Errored e rest -> { result: Err e, rest }
Done vals rest ->
{ before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1
_ ->
when decodeElems bytesWithoutWhitespace [] is
Errored e rest ->
{ result: Err e, rest }
if
maybeEndingBrace == [']']
then
{ result: Ok vals, rest: afterEndingBrace }
else
{ result: Err TooShort, rest }
else
{ result: Err TooShort, rest: bytes }
Done vals rest ->
when rest is
[']', ..] -> { result: Ok vals, rest: List.dropFirst rest }
_ -> { result: Err TooShort, rest }
_ ->
{ result: Err TooShort, rest: bytes }
parseExactChar : List U8, U8 -> DecodeResult {}
parseExactChar = \bytes, char ->
@ -414,6 +498,12 @@ openBrace = \bytes -> parseExactChar bytes '{'
closingBrace : List U8 -> DecodeResult {}
closingBrace = \bytes -> parseExactChar bytes '}'
openBracket : List U8 -> DecodeResult {}
openBracket = \bytes -> parseExactChar bytes '['
closingBracket : List U8 -> DecodeResult {}
closingBracket = \bytes -> parseExactChar bytes ']'
recordKey : List U8 -> DecodeResult Str
recordKey = \bytes -> jsonString bytes
@ -463,3 +553,92 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
when finalizer endStateResult is
Ok val -> { result: Ok val, rest: afterRecordBytes }
Err e -> { result: Err e, rest: afterRecordBytes }
decodeTuple = \initialState, stepElem, finalizer -> Decode.custom \initialBytes, @Json {} ->
# NB: the stepper function must be passed explicitly until #2894 is resolved.
decodeElems = \stepper, state, index, bytes ->
{ val: newState, rest: beforeCommaOrBreak } <- tryDecode
(
when stepper state index is
TooLong ->
{ rest: beforeCommaOrBreak } <- bytes |> anything |> tryDecode
{ result: Ok state, rest: beforeCommaOrBreak }
Next decoder ->
Decode.decodeWith bytes decoder (@Json {})
)
{ result: commaResult, rest: nextBytes } = comma beforeCommaOrBreak
when commaResult is
Ok {} -> decodeElems stepElem newState (index + 1) nextBytes
Err _ -> { result: Ok newState, rest: nextBytes }
{ rest: afterBracketBytes } <- initialBytes |> openBracket |> tryDecode
{ val: endStateResult, rest: beforeClosingBracketBytes } <- decodeElems stepElem initialState 0 afterBracketBytes |> tryDecode
{ rest: afterTupleBytes } <- beforeClosingBracketBytes |> closingBracket |> tryDecode
when finalizer endStateResult is
Ok val -> { result: Ok val, rest: afterTupleBytes }
Err e -> { result: Err e, rest: afterTupleBytes }
# Helper to eat leading Json whitespace characters
eatWhitespace = \input ->
when input is
[' ', ..] -> eatWhitespace (List.dropFirst input)
['\n', ..] -> eatWhitespace (List.dropFirst input)
['\r', ..] -> eatWhitespace (List.dropFirst input)
['\t', ..] -> eatWhitespace (List.dropFirst input)
_ -> input
# Test eating Json whitespace
expect
input = Str.toUtf8 " \n\r\tabc"
actual = eatWhitespace input
expected = Str.toUtf8 "abc"
actual == expected
# Test json string decoding with escapes
expect
input = Str.toUtf8 "\"a\r\nbc\\\"xz\""
expected = Ok "a\r\nbc\\\"xz"
actual = Decode.fromBytes input fromUtf8
actual == expected
# Test json string encoding with escapes
expect
input = "a\r\nbc\\\"xz"
expected = Str.toUtf8 "\"a\r\nbc\\\"xz\""
actual = Encode.toBytes input toUtf8
actual == expected
# Test json array decode empty list
expect
input = Str.toUtf8 "[ ]"
actual : Result (List U8) _
actual = Decode.fromBytes input fromUtf8
expected = Ok []
actual == expected
# Test json array decoding into integers
expect
input = Str.toUtf8 "[ 1,\n2,\t3]"
actual : Result (List U8) _
actual = Decode.fromBytes input fromUtf8
expected = Ok [1, 2, 3]
actual == expected
# Test json array decoding into strings ignoring whitespace around values
expect
input = Str.toUtf8 "[\r\"one\" ,\t\"two\"\n,\n\"3\"\t]"
actual = Decode.fromBytes input fromUtf8
expected = Ok ["one", "two", "3"]
actual == expected

View file

@ -62,6 +62,7 @@ interface List
sortAsc,
sortDesc,
reserve,
releaseExcessCapacity,
walkBackwardsUntil,
countIf,
]
@ -73,11 +74,11 @@ interface List
## Types
## A sequential list of values.
##
## >>> [1, 2, 3] # a list of numbers
## >>> ["a", "b", "c"] # a list of strings
## >>> [[1.1], [], [2.2, 3.3]] # a list of lists of numbers
##
## ```
## [1, 2, 3] # a list of numbers
## ["a", "b", "c"] # a list of strings
## [[1.1], [], [2.2, 3.3]] # a list of lists of numbers
## ```
## The maximum size of a [List] is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
@ -105,11 +106,11 @@ interface List
## will be immediately freed.
##
## Let's look at an example.
## ```
## ratings = [5, 4, 3]
##
## ratings = [5, 4, 3]
##
## { foo: ratings, bar: ratings }
##
## { foo: ratings, bar: ratings }
## ```
## The first line binds the name `ratings` to the list `[5, 4, 3]`. The list
## begins with a refcount of 1, because so far only `ratings` is referencing it.
##
@ -118,14 +119,14 @@ interface List
## refcount getting incremented from 1 to 3.
##
## Let's turn this example into a function.
## ```
## getRatings = \first ->
## ratings = [first, 4, 3]
##
## getRatings = \first ->
## ratings = [first, 4, 3]
##
## { foo: ratings, bar: ratings }
##
## getRatings 5
## { foo: ratings, bar: ratings }
##
## getRatings 5
## ```
## At the end of the `getRatings` function, when the record gets returned,
## the original `ratings =` binding has gone out of scope and is no longer
## accessible. (Trying to reference `ratings` outside the scope of the
@ -140,23 +141,23 @@ interface List
## list, and that list has a refcount of 2.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
## ```
## getRatings = \first ->
## ratings = [first, 4, 3]
##
## getRatings = \first ->
## ratings = [first, 4, 3]
##
## { foo: ratings, bar: ratings }
##
## (getRatings 5).bar
## { foo: ratings, bar: ratings }
##
## (getRatings 5).bar
## ```
## Now, when this expression returns, only the `bar` field of the record will
## be returned. This will mean that the `foo` field becomes inaccessible, causing
## the list's refcount to get decremented from 2 to 1. At this point, the list is back
## where it started: there is only 1 reference to it.
##
## Finally let's suppose the final line were changed to this:
##
## List.first (getRatings 5).bar
##
## ```
## List.first (getRatings 5).bar
## ```
## This call to [List.first] means that even the list in the `bar` field has become
## inaccessible. As such, this line will cause the list's refcount to get
## decremented all the way to 0. At that point, nothing is referencing the list
@ -167,26 +168,26 @@ interface List
## and then with a list of lists, to see how they differ.
##
## Here's the example using a list of numbers.
## ```
## nums = [1, 2, 3, 4, 5, 6, 7]
##
## nums = [1, 2, 3, 4, 5, 6, 7]
##
## first = List.first nums
## last = List.last nums
##
## first
## first = List.first nums
## last = List.last nums
##
## first
## ```
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
##
## Here's the equivalent code with a list of lists:
## ```
## lists = [[1], [2, 3], [], [4, 5, 6, 7]]
##
## lists = [[1], [2, 3], [], [4, 5, 6, 7]]
## first = List.first lists
## last = List.last lists
##
## first = List.first lists
## last = List.last lists
##
## first
##
## TODO explain how in the former example, when we go to free `nums` at the end,
## first
## ```
## **TODO** explain how in the former example, when we go to free `nums` at the end,
## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement
## the refcounts of each of its contained lists - because they, too, have
@ -206,10 +207,11 @@ interface List
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
## Check if the list is empty.
## ```
## List.isEmpty [1, 2, 3]
##
## >>> List.isEmpty [1, 2, 3]
##
## >>> List.isEmpty []
## List.isEmpty []
## ```
isEmpty : List a -> Bool
isEmpty = \list ->
List.len list == 0
@ -237,9 +239,9 @@ replace = \list, index, newValue ->
{ list, value: newValue }
## Replaces the element at the given index with a replacement.
##
## >>> List.set ["a", "b", "c"] 1 "B"
##
## ```
## List.set ["a", "b", "c"] 1 "B"
## ```
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
@ -249,11 +251,12 @@ set = \list, index, value ->
(List.replace list index value).list
## Add a single element to the end of a list.
## ```
## List.append [1, 2, 3] 4
##
## >>> List.append [1, 2, 3] 4
##
## >>> [0, 1, 2]
## >>> |> List.append 3
## [0, 1, 2]
## |> List.append 3
## ```
append : List a, a -> List a
append = \list, element ->
list
@ -268,11 +271,12 @@ append = \list, element ->
appendUnsafe : List a, a -> List a
## Add a single element to the beginning of a list.
## ```
## List.prepend [1, 2, 3] 0
##
## >>> List.prepend [1, 2, 3] 0
##
## >>> [2, 3, 4]
## >>> |> List.prepend 1
## [2, 3, 4]
## |> List.prepend 1
## ```
prepend : List a, a -> List a
## Returns the length of the list - the number of elements it contains.
@ -288,12 +292,17 @@ withCapacity : Nat -> List a
## Enlarge the list for at least capacity additional elements
reserve : List a, Nat -> List a
## Shrink the memory footprint of a list such that it's capacity and length are equal.
## Note: This will also convert seamless slices to regular lists.
releaseExcessCapacity : List a -> List a
## Put two lists together.
## ```
## List.concat [1, 2, 3] [4, 5]
##
## >>> List.concat [1, 2, 3] [4, 5]
##
## >>> [0, 1, 2]
## >>> |> List.concat [3, 4]
## [0, 1, 2]
## |> List.concat [3, 4]
## ```
concat : List a, List a -> List a
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
@ -306,17 +315,15 @@ last = \list ->
## A list with a single element in it.
##
## This is useful in pipelines, like so:
##
## websites =
## Str.concat domain ".com"
## |> List.single
##
## ```
## websites =
## Str.concat domain ".com"
## |> List.single
## ```
single : a -> List a
single = \x -> [x]
## Returns a list with the given length, where every element is the given value.
##
##
repeat : a, Nat -> List a
repeat = \value, count ->
repeatHelp value count (List.withCapacity count)
@ -329,8 +336,9 @@ repeatHelp = \value, count, accum ->
accum
## Returns the list with its elements reversed.
##
## >>> List.reverse [1, 2, 3]
## ```
## List.reverse [1, 2, 3]
## ```
reverse : List a -> List a
reverse = \list ->
reverseHelp list 0 (Num.subSaturated (List.len list) 1)
@ -342,12 +350,11 @@ reverseHelp = \list, left, right ->
list
## Join the given lists together into one list.
##
## >>> List.join [[1, 2, 3], [4, 5], [], [6, 7]]
##
## >>> List.join [[], []]
##
## >>> List.join []
## ```
## List.join [[1, 2, 3], [4, 5], [], [6, 7]]
## List.join [[], []]
## List.join []
## ```
join : List (List a) -> List a
join = \lists ->
totalLength =
@ -366,10 +373,10 @@ contains = \list, needle ->
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
##
## [2, 4, 8]
## |> List.walk 0 Num.add
##
## ```
## [2, 4, 8]
## |> List.walk 0 Num.add
## ```
## This returns 14 because:
## * `state` starts at 0
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
@ -385,10 +392,10 @@ contains = \list, needle ->
## 6 | 8 | 14
##
## The following returns -6:
##
## [1, 2, 3]
## |> List.walk 0 Num.sub
##
## ```
## [1, 2, 3]
## |> List.walk 0 Num.sub
## ```
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
@ -494,9 +501,9 @@ all = \list, predicate ->
## Run the given function on each element of a list, and return all the
## elements for which the function returned `Bool.true`.
##
## >>> List.keepIf [1, 2, 3, 4] (\num -> num > 2)
##
## ```
## List.keepIf [1, 2, 3, 4] (\num -> num > 2)
## ```
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
@ -531,9 +538,9 @@ keepIfHelp = \list, predicate, kept, index, length ->
## Run the given function on each element of a list, and return all the
## elements for which the function returned `Bool.false`.
##
## >>> List.dropIf [1, 2, 3, 4] (\num -> num > 2)
##
## ```
## List.dropIf [1, 2, 3, 4] (\num -> num > 2)
## ```
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
@ -556,12 +563,13 @@ countIf = \list, predicate ->
## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
## ```
## List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last
##
## >>> List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepOks ["", "a", "bc", "", "d", "ef", ""]
## List.keepOks ["", "a", "bc", "", "d", "ef", ""]
## ```
keepOks : List before, (before -> Result after *) -> List after
keepOks = \list, toResult ->
walker = \accum, element ->
@ -573,12 +581,13 @@ keepOks = \list, toResult ->
## This works like [List.map], except only the transformed values that are
## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped.
## ```
## List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last
##
## >>> List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepErrs ["", "a", "bc", "", "d", "ef", ""]
## List.keepErrs ["", "a", "bc", "", "d", "ef", ""]
## ```
keepErrs : List before, (before -> Result * after) -> List after
keepErrs = \list, toResult ->
walker = \accum, element ->
@ -590,10 +599,11 @@ keepErrs = \list, toResult ->
## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values.
## ```
## List.map [1, 2, 3] (\num -> num + 1)
##
## > List.map [1, 2, 3] (\num -> num + 1)
##
## > List.map ["", "a", "bc"] Str.isEmpty
## List.map ["", "a", "bc"] Str.isEmpty
## ```
map : List a, (a -> b) -> List b
## Run a transformation function on the first element of each list,
@ -602,8 +612,9 @@ map : List a, (a -> b) -> List b
##
## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`:
##
## >>> zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
## ```
## zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
## ```
map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list,
@ -640,92 +651,110 @@ mapWithIndexHelp = \src, dest, func, index, length ->
## Returns a list of all the integers between `start` and `end`.
##
## To include the `start` and `end` integers themselves, use `At` like so:
##
## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5]
##
## ```
## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5]
## ```
## To exclude them, use `After` and `Before`, like so:
##
## List.range { start: After 2, end: Before 5 } # returns [3, 4]
##
## ```
## List.range { start: After 2, end: Before 5 } # returns [3, 4]
## ```
## You can have the list end at a certain length rather than a certain integer:
##
## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9]
##
## ```
## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9]
## ```
## If `step` is specified, each integer increases by that much. (`step: 1` is the default.)
##
## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6]
##
## ```
## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6]
## ```
## List.range will also generate a reversed list if step is negative or end comes before start:
## ```
## List.range { start: At 5, end: At 2 } # returns [5, 4, 3, 2]
## ```
## All of these options are compatible with the others. For example, you can use `At` or `After`
## with `start` regardless of what `end` and `step` are set to.
range : _
range = \{ start, end, step ? 0 } ->
{ incByStep, stepIsPositive } =
{ calcNext, stepIsPositive } =
if step == 0 then
when T start end is
T (At x) (At y) | T (At x) (Before y) | T (After x) (At y) | T (After x) (Before y) ->
if x < y then
{
incByStep: \i -> i + 1,
calcNext: \i -> Num.addChecked i 1,
stepIsPositive: Bool.true,
}
else
{
incByStep: \i -> i - 1,
calcNext: \i -> Num.subChecked i 1,
stepIsPositive: Bool.false,
}
T (At _) (Length _) | T (After _) (Length _) ->
{
incByStep: \i -> i + 1,
calcNext: \i -> Num.addChecked i 1,
stepIsPositive: Bool.true,
}
else
{
incByStep: \i -> i + step,
calcNext: \i -> Num.addChecked i step,
stepIsPositive: step > 0,
}
inclusiveStart =
when start is
At x -> x
After x -> incByStep x
At x -> Ok x
After x -> calcNext x
when end is
At at ->
isComplete =
isValid =
if stepIsPositive then
\i -> i > at
\i -> i <= at
else
\i -> i < at
\i -> i >= at
# TODO: switch to List.withCapacity
rangeHelp [] inclusiveStart incByStep isComplete
rangeHelp [] inclusiveStart calcNext isValid
Before before ->
isComplete =
isValid =
if stepIsPositive then
\i -> i >= before
\i -> i < before
else
\i -> i <= before
\i -> i > before
# TODO: switch to List.withCapacity
rangeHelp [] inclusiveStart incByStep isComplete
rangeHelp [] inclusiveStart calcNext isValid
Length l ->
rangeLengthHelp (List.withCapacity l) inclusiveStart l incByStep
rangeLengthHelp (List.withCapacity l) inclusiveStart l calcNext
rangeHelp = \accum, i, incByStep, isComplete ->
if isComplete i then
accum
else
# TODO: change this to List.appendUnsafe once capacity is set correctly
rangeHelp (List.append accum i) (incByStep i) incByStep isComplete
rangeHelp = \accum, i, calcNext, isValid ->
when i is
Ok val ->
if isValid val then
# TODO: change this to List.appendUnsafe once capacity is set correctly
rangeHelp (List.append accum val) (calcNext val) calcNext isValid
else
accum
rangeLengthHelp = \accum, i, remaining, incByStep ->
Err _ ->
# We went past the end of the numeric range and there is no next.
# return the generated list.
accum
rangeLengthHelp = \accum, i, remaining, calcNext ->
if remaining == 0 then
accum
else
rangeLengthHelp (List.appendUnsafe accum i) (incByStep i) (remaining - 1) incByStep
when i is
Ok val ->
rangeLengthHelp (List.appendUnsafe accum val) (calcNext val) (remaining - 1) calcNext
Err _ ->
# We went past the end of the numeric range and there is no next.
# The list is not the correct length yet, so we must crash.
crash "List.range: failed to generate enough elements to fill the range before overflowing the numeric type"
expect
List.range { start: At 0, end: At 4 } == [0, 1, 2, 3, 4]
@ -754,6 +783,18 @@ expect
expect
List.range { start: At 4, end: Length 5, step: -3 } == [4, 1, -2, -5, -8]
expect
List.range { start: After 250u8, end: At 255 } == [251, 252, 253, 254, 255]
expect
List.range { start: After 250u8, end: At 255, step: 10 } == []
expect
List.range { start: After 250u8, end: At 245, step: 10 } == []
expect
List.range { start: At 4, end: At 0 } == [4, 3, 2, 1, 0]
## Sort with a custom comparison function
sortWith : List a, (a, a -> [LT, EQ, GT]) -> List a
@ -795,14 +836,14 @@ dropLast = \list ->
List.dropAt list (Num.subSaturated (List.len list) 1)
## Returns the given number of elements from the beginning of the list.
##
## >>> List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4
##
## ```
## List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4
## ```
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeFirst [1, 2] 5
##
## ```
## List.takeFirst [1, 2] 5
## ```
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
@ -810,28 +851,19 @@ dropLast = \list ->
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It sets the list's length
## to the given length value, and frees the leftover elements. This runs very
## slightly faster than `List.takeLast`.
##
## In fact, `List.takeFirst list 1` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
takeFirst = \list, outputLength ->
List.sublist list { start: 0, len: outputLength }
## Returns the given number of elements from the end of the list.
##
## >>> List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4
##
## ```
## List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4
## ```
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeLast [1, 2] 5
##
## ```
## List.takeLast [1, 2] 5
## ```
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
@ -839,16 +871,6 @@ takeFirst = \list, outputLength ->
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It moves the list's
## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as
## `List.takeFirst` on a Unique list.
##
## In fact, `List.takeLast list 1` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
takeLast = \list, outputLength ->
List.sublist list { start: Num.subSaturated (List.len list) outputLength, len: outputLength }
@ -971,13 +993,13 @@ findLastIndex = \list, matches ->
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
##
## >>> List.sublist [1, 2, 3] { start: 4, len: 0 }
##
## ```
## List.sublist [1, 2, 3] { start: 4, len: 0 }
## ```
## If more elements are requested than exist in the list, returns as many as it can.
##
## >>> List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 }
##
## ```
## List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 }
## ```
## > If you want a sublist which goes all the way to the end of the list, no
## > matter how long the list is, `List.takeLast` can do that more efficiently.
##
@ -993,7 +1015,9 @@ sublist = \list, config ->
sublistLowlevel : List elem, Nat, Nat -> List elem
## Intersperses `sep` between the elements of `list`
## >>> List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3]
## ```
## List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3]
## ```
intersperse : List elem, elem -> List elem
intersperse = \list, sep ->
capacity = 2 * List.len list
@ -1051,8 +1075,9 @@ split = \elements, userSplitIndex ->
## Returns the elements before the first occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] }
## ```
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Z, Baz] }
## ```
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitFirst = \list, delimiter ->
when List.findFirstIndex list (\elem -> elem == delimiter) is
@ -1066,8 +1091,9 @@ splitFirst = \list, delimiter ->
## Returns the elements before the last occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] }
## ```
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Z, Bar], after: [Baz] }
## ```
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitLast = \list, delimiter ->
when List.findLastIndex list (\elem -> elem == delimiter) is

View file

@ -68,6 +68,9 @@ interface Num
compare,
pow,
powInt,
countLeadingZeroBits,
countTrailingZeroBits,
countOneBits,
addWrap,
addChecked,
addSaturated,
@ -86,6 +89,8 @@ interface Num
intCast,
bytesToU16,
bytesToU32,
bytesToU64,
bytesToU128,
divCeil,
divCeilChecked,
divTrunc,
@ -151,9 +156,9 @@ interface Num
## Represents a number that could be either an [Int] or a [Frac].
##
## This is useful for functions that can work on either, for example [Num.add], whose type is:
##
## add : Num a, Num a -> Num a
##
## ```
## add : Num a, Num a -> Num a
## ```
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`.
##
@ -191,9 +196,9 @@ interface Num
##
## If this default of [I64] is not big enough for your purposes,
## you can add an `i128` to the end of the number literal, like so:
##
## >>> Num.toStr 5_000_000_000i128
##
## ```
## Num.toStr 5_000_000_000i128
## ```
## This `i128` suffix specifies that you want this number literal to be
## an [I128] instead of a `Num *`. All the other numeric types have
## suffixes just like `i128`; here are some other examples:
@ -234,7 +239,7 @@ Num range := range
##
## This pattern continues up to [U128] and [I128].
##
## ## Performance notes
## ## Performance Details
##
## In general, using smaller numeric sizes means your program will use less memory.
## However, if a mathematical operation results in an answer that is too big
@ -259,15 +264,11 @@ Num range := range
##
## All number literals without decimal points are compatible with [Int] values.
##
## >>> 1
##
## >>> 0
##
## You can optionally put underscores in your [Int] literals.
## They have no effect on the number's value, but can make large numbers easier to read.
##
## >>> 1_000_000
##
## ```
## 1_000_000
## ```
## Integers come in two flavors: *signed* and *unsigned*.
##
## * *Unsigned* integers can never be negative. The lowest value they can hold is zero.
@ -342,16 +343,16 @@ Int range : Num (Integer range)
##
## If you don't specify a type, Roc will default to using [Dec] because it's
## the least error-prone overall. For example, suppose you write this:
##
## wasItPrecise = 0.1 + 0.2 == 0.3
##
## ```
## wasItPrecise = 0.1 + 0.2 == 0.3
## ```
## The value of `wasItPrecise` here will be `Bool.true`, because Roc uses [Dec]
## by default when there are no types specified.
##
## In contrast, suppose we use `f32` or `f64` for one of these numbers:
##
## wasItPrecise = 0.1f64 + 0.2 == 0.3
##
## ```
## wasItPrecise = 0.1f64 + 0.2 == 0.3
## ```
## Here, `wasItPrecise` will be `Bool.false` because the entire calculation will have
## been done in a base-2 floating point calculation, which causes noticeable
## precision loss in this case.
@ -380,7 +381,7 @@ Int range : Num (Integer range)
## Whenever a function in this module could return one of these values, that
## possibility is noted in the function's documentation.
##
## ## Performance Notes
## ## Performance Details
##
## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32]
## for addition and subtraction. For example, [F32] and [F64] do addition using
@ -480,7 +481,7 @@ F32 : Num (FloatingPoint Binary32)
## details, below), and decimal precision loss isn't as big a concern when
## dealing with screen coordinates as it is when dealing with currency.
##
## ## Performance
## ## Performance Details
##
## [Dec] typically takes slightly less time than [F64] to perform addition and
## subtraction, but 10-20 times longer to perform multiplication and division.
@ -492,15 +493,14 @@ Dec : Num (FloatingPoint Decimal)
##
## This is the same as calling `Num.format {}` - so for more details on
## exact formatting, see `Num.format`.
##
## >>> Num.toStr 42
##
## ```
## Num.toStr 42
## ```
## Only [Frac] values will include a decimal point, and they will always include one.
##
## >>> Num.toStr 4.2
##
## >>> Num.toStr 4.0
##
## ```
## Num.toStr 4.2
## Num.toStr 4.0
## ```
## When this function is given a non-[finite](Num.isFinite)
## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`.
##
@ -510,6 +510,8 @@ intCast : Int a -> Int b
bytesToU16Lowlevel : List U8, Nat -> U16
bytesToU32Lowlevel : List U8, Nat -> U32
bytesToU64Lowlevel : List U8, Nat -> U64
bytesToU128Lowlevel : List U8, Nat -> U128
bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds]
bytesToU16 = \bytes, index ->
@ -531,6 +533,26 @@ bytesToU32 = \bytes, index ->
else
Err OutOfBounds
bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds]
bytesToU64 = \bytes, index ->
# we need at least 7 more bytes
offset = 7
if index + offset < List.len bytes then
Ok (bytesToU64Lowlevel bytes index)
else
Err OutOfBounds
bytesToU128 : List U8, Nat -> Result U128 [OutOfBounds]
bytesToU128 = \bytes, index ->
# we need at least 15 more bytes
offset = 15
if index + offset < List.len bytes then
Ok (bytesToU128Lowlevel bytes index)
else
Err OutOfBounds
compare : Num a, Num a -> [LT, EQ, GT]
## Returns `Bool.true` if the first number is less than the second.
@ -539,9 +561,10 @@ compare : Num a, Num a -> [LT, EQ, GT]
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
##
## >>> 5
## >>> |> Num.isLt 6
## ```
## 5
## |> Num.isLt 6
## ```
isLt : Num a, Num a -> Bool
## Returns `Bool.true` if the first number is greater than the second.
@ -550,9 +573,10 @@ isLt : Num a, Num a -> Bool
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
##
## >>> 6
## >>> |> Num.isGt 5
## ```
## 6
## |> Num.isGt 5
## ```
isGt : Num a, Num a -> Bool
## Returns `Bool.true` if the first number is less than or equal to the second.
@ -601,15 +625,15 @@ toFrac : Num * -> Frac *
## * For a positive number, returns the same number.
## * For a negative number, returns the same number except positive.
## * For zero, returns zero.
## ```
## Num.abs 4
##
## >>> Num.abs 4
## Num.abs -2.5
##
## >>> Num.abs -2.5
##
## >>> Num.abs 0
##
## >>> Num.abs 0.0
## Num.abs 0
##
## Num.abs 0.0
## ```
## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values.
##
## For example, calling #Num.abs on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow.
@ -620,15 +644,15 @@ toFrac : Num * -> Frac *
abs : Num a -> Num a
## Return a negative number when given a positive one, and vice versa.
## ```
## Num.neg 5
##
## >>> Num.neg 5
## Num.neg -2.5
##
## >>> Num.neg -2.5
##
## >>> Num.neg 0
##
## >>> Num.neg 0.0
## Num.neg 0
##
## Num.neg 0.0
## ```
## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values.
##
## For example, calling #Num.neg on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow.
@ -645,16 +669,16 @@ neg : Num a -> Num a
## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
## `a + b` is shorthand for `Num.add a b`.
## ```
## 5 + 7
##
## >>> 5 + 7
##
## >>> Num.add 5 7
##
## Num.add 5 7
## ```
## `Num.add` can be convenient in pipelines.
##
## >>> Frac.pi
## >>> |> Num.add 1.0
##
## ```
## Frac.pi
## |> Num.add 1.0
## ```
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
@ -666,16 +690,16 @@ add : Num a, Num a -> Num a
## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
## `a - b` is shorthand for `Num.sub a b`.
## ```
## 7 - 5
##
## >>> 7 - 5
##
## >>> Num.sub 7 5
##
## Num.sub 7 5
## ```
## `Num.sub` can be convenient in pipelines.
##
## >>> Frac.pi
## >>> |> Num.sub 2.0
##
## ```
## Frac.pi
## |> Num.sub 2.0
## ```
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
@ -687,16 +711,18 @@ sub : Num a, Num a -> Num a
## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
## `a * b` is shorthand for `Num.mul a b`.
## ```
## 5 * 7
##
## >>> 5 * 7
##
## >>> Num.mul 5 7
## Num.mul 5 7
## ```
##
## `Num.mul` can be convenient in pipelines.
##
## >>> Frac.pi
## >>> |> Num.mul 2.0
##
## ```
## Frac.pi
## |> Num.mul 2.0
## ```
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
@ -731,14 +757,15 @@ atan : Frac a -> Frac a
## > this standard, deviating from these rules has a significant performance
## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## > access to hardware-accelerated performance, Roc follows these rules exactly.
## ```
## Num.sqrt 4.0
##
## >>> Num.sqrt 4.0
## Num.sqrt 1.5
##
## >>> Num.sqrt 1.5
## Num.sqrt 0.0
##
## >>> Num.sqrt 0.0
##
## >>> Num.sqrt -4.0f64
## Num.sqrt -4.0f64
## ```
sqrt : Frac a -> Frac a
sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative]
@ -748,6 +775,7 @@ sqrtChecked = \x ->
else
Ok (Num.sqrt x)
## Natural logarithm
log : Frac a -> Frac a
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]
@ -778,15 +806,16 @@ logChecked = \x ->
##
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
## one of the functions in this module like #toDec.
## ```
## 5.0 / 7.0
##
## >>> 5.0 / 7.0
##
## >>> Num.div 5 7
##
## Num.div 5 7
## ```
## `Num.div` can be convenient in pipelines.
##
## >>> Num.pi
## >>> |> Num.div 2.0
## ```
## Num.pi
## |> Num.div 2.0
## ```
div : Frac a, Frac a -> Frac a
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]
@ -812,15 +841,15 @@ divCeilChecked = \a, b ->
## Division by zero is undefined in mathematics. As such, you should make
## sure never to pass zero as the denomaintor to this function! If you do,
## it will crash.
## ```
## 5 // 7
##
## >>> 5 // 7
## Num.divTrunc 5 7
##
## >>> Num.divTrunc 5 7
##
## >>> 8 // -3
##
## >>> Num.divTrunc 8 -3
## 8 // -3
##
## Num.divTrunc 8 -3
## ```
divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
@ -833,14 +862,15 @@ divTruncChecked = \a, b ->
## Obtain the remainder (truncating modulo) from the division of two integers.
##
## `a % b` is shorthand for `Num.rem a b`.
## ```
## 5 % 7
##
## >>> 5 % 7
## Num.rem 5 7
##
## >>> Num.rem 5 7
## -8 % -3
##
## >>> -8 % -3
##
## >>> Num.rem -8 -3
## Num.rem -8 -3
## ```
rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [DivByZero]
@ -860,24 +890,24 @@ bitwiseOr : Int a, Int a -> Int a
##
## The least significant bits always become 0. This means that shifting left is
## like multiplying by factors of two for unsigned integers.
## ```
## shiftLeftBy 0b0000_0011 2 == 0b0000_1100
##
## >>> shiftLeftBy 0b0000_0011 2 == 0b0000_1100
##
## >>> 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
##
## 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
## ```
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
shiftLeftBy : Int a, U8 -> Int a
## Bitwise arithmetic shift of a number by another
##
## The most significant bits are copied from the current.
## ```
## shiftRightBy 0b0000_0011 2 == 0b0000_1100
##
## >>> shiftRightBy 0b0000_0011 2 == 0b0000_1100
##
## >>> 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
## 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
##
## 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
## ```
## In some languages `shiftRightBy` is implemented as a binary operator `>>>`.
shiftRightBy : Int a, U8 -> Int a
@ -885,13 +915,13 @@ shiftRightBy : Int a, U8 -> Int a
##
## The most significant bits always become 0. This means that shifting left is
## like dividing by factors of two for unsigned integers.
## ```
## shiftRightBy 0b0010_1000 2 == 0b0000_1010
##
## >>> shiftRightBy 0b0010_1000 2 == 0b0000_1010
##
## >>> 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
## 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
##
## 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
## ```
## In some languages `shiftRightBy` is implemented as a binary operator `>>`.
shiftRightZfBy : Int a, U8 -> Int a
@ -912,21 +942,60 @@ pow : Frac a, Frac a -> Frac a
##
## For a [Frac] alternative to this function, which supports negative exponents,
## see #Num.exp.
## ```
## Num.exp 5 0
##
## >>> Num.exp 5 0
## Num.exp 5 1
##
## >>> Num.exp 5 1
## Num.exp 5 2
##
## >>> Num.exp 5 2
##
## >>> Num.exp 5 6
##
## ## Performance Notes
## Num.exp 5 6
## ```
## ## Performance Details
##
## Be careful! It is very easy for this function to produce an answer
## so large it causes an overflow.
powInt : Int a, Int a -> Int a
## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer.
##
## ```
## Num.countLeadingZeroBits 0b0001_1100u8
##
## 3
##
## Num.countLeadingZeroBits 0b0000_0000u8
##
## 8
## ```
countLeadingZeroBits : Int a -> Nat
## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer.
##
## ```
## Num.countTrailingZeroBits 0b0001_1100u8
##
## 2
##
## Num.countTrailingZeroBits 0b0000_0000u8
##
## 8
## ```
countTrailingZeroBits : Int a -> Nat
## Counts the number of set bits in an integer.
##
## ```
## Num.countOneBits 0b0001_1100u8
##
## 3
##
## Num.countOneBits 0b0000_0000u8
##
## 0
## ```
countOneBits : Int a -> Nat
addWrap : Int range, Int range -> Int range
## Add two numbers, clamping on the maximum representable number rather than
@ -1261,164 +1330,3 @@ toU128Checked : Int * -> Result U128 [OutOfBounds]
toNatChecked : Int * -> Result Nat [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds]
# Special Floating-Point operations
## When given a [F64] or [F32] value, returns `Bool.false` if that value is
## [*NaN*](Num.isNaN), ∞ or -∞, and `Bool.true` otherwise.
##
## Always returns `Bool.true` when given a [Dec].
##
## This is the opposite of #isInfinite, except when given [*NaN*](Num.isNaN). Both
## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN).
# isFinite : Frac * -> Bool
## When given a [F64] or [F32] value, returns `Bool.true` if that value is either
## ∞ or -∞, and `Bool.false` otherwise.
##
## Always returns `Bool.false` when given a [Dec].
##
## This is the opposite of #isFinite, except when given [*NaN*](Num.isNaN). Both
## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN).
# isInfinite : Frac * -> Bool
## When given a [F64] or [F32] value, returns `Bool.true` if that value is
## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `Bool.false` otherwise.
##
## Always returns `Bool.false` when given a [Dec].
##
## >>> Num.isNaN 12.3
##
## >>> Num.isNaN (Num.pow -1 0.5)
##
## *NaN* is unusual from other numberic values in that:
## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `Bool.false` if either argument is *NaN*.
## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `Bool.false` if either argument is *NaN*.
##
## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
## floating point standard. Because almost all modern processors are built to
## this standard, deviating from these rules has a significant performance
## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## access to hardware-accelerated performance, Roc follows these rules exactly.
##
## Note that you should never put a *NaN* into a [Set], or use it as the key in
## a [Dict]. The result is entries that can never be removed from those
## collections! See the documentation for [Set.insert] and [Dict.insert] for details.
# isNaN : Frac * -> Bool
## Returns the higher of two numbers.
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
# max : Num a, Num a -> Num a
## Returns the lower of two numbers.
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
# min : Num a, Num a -> Num a
# Branchless implementation that works for all numeric types:
#
# let is_lt = arg1 < arg2;
# let is_eq = arg1 == arg2;
# return (is_lt as i8 - is_eq as i8) + 1;
#
# 1, 1 -> (0 - 1) + 1 == 0 # Eq
# 5, 1 -> (0 - 0) + 1 == 1 # Gt
# 1, 5 -> (1 - 0) + 1 == 2 # Lt
## Returns `Lt` if the first number is less than the second, `Gt` if
## the first is greater than the second, and `Eq` if they're equal.
##
## Although this can be passed to `List.sort`, you'll get better performance
## by using `List.sortAsc` or `List.sortDesc` instead.
# compare : Num a, Num a -> [Lt, Eq, Gt]
## [Endianness](https://en.wikipedia.org/wiki/Endianness)
# Endi : [Big, Little, Native]
## The `Endi` argument does not matter for [U8] and [I8], since they have
## only one byte.
# toBytes : Num *, Endi -> List U8
## when Num.parseBytes bytes Big is
## Ok { val: f64, rest } -> ...
## Err (ExpectedNum (Frac Binary64)) -> ...
# parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ExpectedNum a]*
## when Num.fromBytes bytes Big is
## Ok f64 -> ...
## Err (ExpectedNum (Frac Binary64)) -> ...
# fromBytes : List U8, Endi -> Result (Num a) [ExpectedNum a]*
# Bit shifts
## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) left.
##
## `a << b` is shorthand for `Num.shl a b`.
# shl : Int a, Int a -> Int a
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) left.
##
## This is called `shlWrap` because any bits shifted
## off the beginning of the number will be wrapped around to
## the end. (In contrast, #shl replaces discarded bits with zeroes.)
# shlWrap : Int a, Int a -> Int a
## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) right.
##
## `a >> b` is shorthand for `Num.shr a b`.
# shr : Int a, Int a -> Int a
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right.
##
## This is called `shrWrap` because any bits shifted
## off the end of the number will be wrapped around to
## the beginning. (In contrast, #shr replaces discarded bits with zeroes.)
# shrWrap : Int a, Int a -> Int a
# ## Convert a number into a [Str], formatted with the given options.
# ##
# ## Default options:
# ## * `base: Decimal`
# ## * `notation: Standard`
# ## * `decimalMark: HideForIntegers "."`
# ## * `decimalDigits: { min: 0, max: All }`
# ## * `minIntDigits: 1`
# ## * `wholeSep: { mark: ",", places: 3 }`
# ##
# ## ## Options
# ##
# ##
# ## ### decimalMark
# ##
# ## * `AlwaysShow` always shows the decimal mark, no matter what.
# ## * `HideForIntegers` hides the decimal mark if all the numbers after the decimal mark are 0.
# ##
# ## The [Str] included in either of these represents the mark itself.
# ##
# ## ### `decimalDigits
# ##
# ## With 0 decimal digits, the decimal mark will still be rendered if
# ## `decimalMark` is set to `AlwaysShow`.
# ##
# ## If `max` is less than `min`, then first the number will be truncated to `max`
# ## digits, and then zeroes will be added afterwards until it reaches `min` digits.
# ##
# ## >>> Num.format 1.23 { decPlaces: 0, decPointVis: AlwaysShow }
# ##
# ## ### minIntDigits
# ##
# ## If the integer portion of number is fewer than this many digits, zeroes will
# ## be added in front of it until there are at least `minWholeDigits` digits.
# ##
# ## If this is set to zero, then numbers less than 1 will begin with `"."`
# ## rather than `"0."`.
# ##
# ## ### wholeSep
# ##
# ## Examples:
# ##
# ## In some countries (e.g. USA and UK), a comma is used to separate thousands:
# ## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } }
# ##
# ## Sometimes when rendering bits, it's nice to group them into groups of 4:
# ## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } }
# ##
# ## It's also common to render hexadecimal in groups of 2:
# ## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
# format :
# Num *,
# {
# base ? [Decimal, Hexadecimal, Octal, Binary],
# notation ? [Standard, Scientific],
# decimalMark ? [AlwaysShow Str, HideForIntegers],
# decimalDigits ? { min : U16, max : [All, Trunc U16, Round U16, Floor U16, Ceil U16] },
# minWholeDigits ? U16,
# wholeSep ? { mark : Str, places : U64 }
# }
# -> Str

View file

@ -7,8 +7,9 @@ interface Result
Result ok err : [Ok ok, Err err]
## Return `Bool.true` if the result indicates a success, else return `Bool.false`
##
## >>> Result.isOk (Ok 5)
## ```
## Result.isOk (Ok 5)
## ```
isOk : Result ok err -> Bool
isOk = \result ->
when result is
@ -16,8 +17,9 @@ isOk = \result ->
Err _ -> Bool.false
## Return `Bool.true` if the result indicates a failure, else return `Bool.false`
##
## >>> Result.isErr (Err "uh oh")
## ```
## Result.isErr (Err "uh oh")
## ```
isErr : Result ok err -> Bool
isErr = \result ->
when result is
@ -26,10 +28,10 @@ isErr = \result ->
## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value.
##
## >>> Result.withDefault (Ok 7) 42
##
## >>> Result.withDefault (Err "uh oh") 42
## ```
## Result.withDefault (Ok 7) 42
## Result.withDefault (Err "uh oh") 42
## ```
withDefault : Result ok err, ok -> ok
withDefault = \result, default ->
when result is
@ -37,16 +39,15 @@ withDefault = \result, default ->
Err _ -> default
## If the result is `Ok`, transform the value it holds by running a conversion
## function on it. Then return a new `Ok` holding the transformed value.
## function on it. Then return a new `Ok` holding the transformed value. If the
## result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.
## ```
## Result.map (Ok 12) Num.negate
## Result.map (Err "yipes!") Num.negate
## ```
##
## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.)
##
## >>> Result.map (Ok 12) Num.negate
##
## >>> Result.map (Err "yipes!") Num.negate
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`.
## Functions like `map` are common in Roc; see for example [List.map],
## `Set.map`, and `Dict.map`.
map : Result a err, (a -> b) -> Result b err
map = \result, transform ->
when result is
@ -54,13 +55,12 @@ map = \result, transform ->
Err e -> Err e
## If the result is `Err`, transform the value it holds by running a conversion
## function on it. Then return a new `Err` holding the transformed value.
##
## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.)
##
## >>> Result.mapErr (Err "yipes!") Str.isEmpty
##
## >>> Result.mapErr (Ok 12) Str.isEmpty
## function on it. Then return a new `Err` holding the transformed value. If
## the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.
## ```
## Result.mapErr (Err "yipes!") Str.isEmpty
## Result.mapErr (Ok 12) Str.isEmpty
## ```
mapErr : Result ok a, (a -> b) -> Result ok b
mapErr = \result, transform ->
when result is
@ -68,13 +68,12 @@ mapErr = \result, transform ->
Err e -> Err (transform e)
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result.
##
## (If the result is `Err`, this has no effect. Use `onErr` to transform an `Err`.)
##
## >>> Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
##
## >>> Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
## function on the value the `Ok` holds. Then return that new result. If the
## result is `Err`, this has no effect. Use `onErr` to transform an `Err`.
## ```
## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
## Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
## ```
try : Result a err, (a -> Result b err) -> Result b err
try = \result, transform ->
when result is
@ -82,13 +81,12 @@ try = \result, transform ->
Err e -> Err e
## If the result is `Err`, transform the entire result by running a conversion
## function on the value the `Err` holds. Then return that new result.
##
## (If the result is `Ok`, this has no effect. Use `try` to transform an `Ok`.)
##
## >>> Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum
##
## >>> Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
## function on the value the `Err` holds. Then return that new result. If the
## result is `Ok`, this has no effect. Use `try` to transform an `Ok`.
## ```
## Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum
## Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
## ```
onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr
onErr = \result, transform ->
when result is

View file

@ -4,6 +4,7 @@ interface Set
empty,
single,
walk,
walkUntil,
insert,
len,
remove,
@ -25,6 +26,8 @@ interface Set
# We should have this line above the next has.
# It causes the formatter to fail currently.
# | k has Hash & Eq
## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
## type which stores a collection of unique values, without any ordering
Set k := Dict.Dict k {}
has [
Eq {
@ -43,14 +46,39 @@ isEq = \xs, ys ->
else
Break Bool.false
## Creates a new empty set.
## Creates a new empty `Set`.
## ```
## emptySet = Set.empty {}
## countValues = Set.len emptySet
##
## expect countValues == 0
## ```
empty : {} -> Set k | k has Hash & Eq
empty = \{} -> @Set (Dict.empty {})
## Creates a new `Set` with a single value.
## ```
## singleItemSet = Set.single "Apple"
## countValues = Set.len singleItemSet
##
## expect countValues == 1
## ```
single : k -> Set k | k has Hash & Eq
single = \key ->
Dict.single key {} |> @Set
## Insert a value into a `Set`.
## ```
## fewItemSet =
## Set.empty {}
## |> Set.insert "Apple"
## |> Set.insert "Pear"
## |> Set.insert "Banana"
##
## countValues = Set.len fewItemSet
##
## expect countValues == 3
## ```
insert : Set k, k -> Set k | k has Hash & Eq
insert = \@Set dict, key ->
Dict.insert dict key {} |> @Set
@ -72,6 +100,18 @@ expect
expected == actual
## Counts the number of values in a given `Set`.
## ```
## fewItemSet =
## Set.empty {}
## |> Set.insert "Apple"
## |> Set.insert "Pear"
## |> Set.insert "Banana"
##
## countValues = Set.len fewItemSet
##
## expect countValues == 3
## ```
len : Set k -> Nat | k has Hash & Eq
len = \@Set dict ->
Dict.len dict
@ -88,41 +128,151 @@ expect
actual == 3
## Drops the given element from the set.
## Removes the value from the given `Set`.
## ```
## numbers =
## Set.empty {}
## |> Set.insert 10
## |> Set.insert 20
## |> Set.remove 10
##
## has10 = Set.contains numbers 10
## has20 = Set.contains numbers 20
##
## expect has10 == Bool.false
## expect has20 == Bool.true
## ```
remove : Set k, k -> Set k | k has Hash & Eq
remove = \@Set dict, key ->
Dict.remove dict key |> @Set
## Test if a value is in the `Set`.
## ```
## Fruit : [Apple, Pear, Banana]
##
## fruit : Set Fruit
## fruit =
## Set.single Apple
## |> Set.insert Pear
##
## hasApple = Set.contains fruit Apple
## hasBanana = Set.contains fruit Banana
##
## expect hasApple == Bool.true
## expect hasBanana == Bool.false
## ```
contains : Set k, k -> Bool | k has Hash & Eq
contains = \@Set dict, key ->
Dict.contains dict key
## Retrieve the values in a `Set` as a `List`.
## ```
## numbers : Set U64
## numbers = Set.fromList [1,2,3,4,5]
##
## values = [1,2,3,4,5]
##
## expect Set.toList numbers == values
## ```
toList : Set k -> List k | k has Hash & Eq
toList = \@Set dict ->
Dict.keys dict
## Create a `Set` from a `List` of values.
## ```
## values =
## Set.empty {}
## |> Set.insert Banana
## |> Set.insert Apple
## |> Set.insert Pear
##
## expect Set.fromList [Pear, Apple, Banana] == values
## ```
fromList : List k -> Set k | k has Hash & Eq
fromList = \list ->
initial = @Set (Dict.withCapacity (List.len list))
List.walk list initial insert
## Combine two `Set` collection by keeping the
## [union](https://en.wikipedia.org/wiki/Union_(set_theory))
## of all the values pairs. This means that all of the values in both `Set`s
## will be combined.
## ```
## set1 = Set.single Left
## set2 = Set.single Right
##
## expect Set.union set1 set2 == Set.fromList [Left, Right]
## ```
union : Set k, Set k -> Set k | k has Hash & Eq
union = \@Set dict1, @Set dict2 ->
Dict.insertAll dict1 dict2 |> @Set
## Combine two `Set`s by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
## of all the values pairs. This means that we keep only those values that are
## in both `Set`s.
## ```
## set1 = Set.fromList [Left, Other]
## set2 = Set.fromList [Left, Right]
##
## expect Set.intersection set1 set2 == Set.single Left
## ```
intersection : Set k, Set k -> Set k | k has Hash & Eq
intersection = \@Set dict1, @Set dict2 ->
Dict.keepShared dict1 dict2 |> @Set
## Remove the values in the first `Set` that are also in the second `Set`
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
## of the values. This means that we will be left with only those values that
## are in the first and not in the second.
## ```
## first = Set.fromList [Left, Right, Up, Down]
## second = Set.fromList [Left, Right]
##
## expect Set.difference first second == Set.fromList [Up, Down]
## ```
difference : Set k, Set k -> Set k | k has Hash & Eq
difference = \@Set dict1, @Set dict2 ->
Dict.removeAll dict1 dict2 |> @Set
## Iterate through the values of a given `Set` and build a value.
## ```
## values = Set.fromList ["March", "April", "May"]
##
## startsWithLetterM = \month ->
## when Str.toUtf8 month is
## ['M', ..] -> Bool.true
## _ -> Bool.false
##
## reduce = \state, k ->
## if startsWithLetterM k then
## state + 1
## else
## state
##
## result = Set.walk values 0 reduce
##
## expect result == 2
## ```
walk : Set k, state, (state, k -> state) -> state | k has Hash & Eq
walk = \@Set dict, state, step ->
Dict.walk dict state (\s, k, _ -> step s k)
## Iterate through the values of a given `Set` and build a value, can stop
## iterating part way through the collection.
## ```
## numbers = Set.fromList [1,2,3,4,5,6,42,7,8,9,10]
##
## find42 = \state, k ->
## if k == 42 then
## Break FoundTheAnswer
## else
## Continue state
##
## result = Set.walkUntil numbers NotFound find42
##
## expect result == FoundTheAnswer
## ```
walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state | k has Hash & Eq
walkUntil = \@Set dict, state, step ->
Dict.walkUntil dict state (\s, k, _ -> step s k)

View file

@ -1,14 +1,13 @@
## Working with Unicode strings in Roc.
##
## ### Unicode
## ## Working with Unicode strings in Roc
##
## 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 [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
@ -17,11 +16,11 @@
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling `Str.countGraphemes` on it:
##
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
##
## ```
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
## ```
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
@ -31,23 +30,23 @@
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
##
## "I took the one less traveled by,\nAnd that has made all the difference."
##
## ```
## "I took the one less traveled by,\nAnd that has made all the difference."
## ```
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## Another way you could insert a newlines is by writing `\u(0A)` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
## `\u` escape sequences are always followed by a hexadecimal number inside `(` and `)`
## like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## As another example, `"R\u(6F)c"` is the same string as `"Roc"`, because
## `"\u(6F)"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
## you can write `"R\u(F6)c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
@ -58,12 +57,11 @@
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## ```
## name = "Lee"
## city = "Roctown"
## greeting = "Hello there, \(name)! Welcome to \(city)."
## ```
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
@ -111,6 +109,7 @@ interface Str
splitLast,
walkUtf8WithIndex,
reserve,
releaseExcessCapacity,
appendScalar,
walkScalars,
walkScalarsUntil,
@ -125,7 +124,6 @@ interface Str
Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
]
## Test
Utf8ByteProblem : [
InvalidStartByte,
UnexpectedEndOfSequence,
@ -138,16 +136,18 @@ Utf8ByteProblem : [
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
##
## expect Str.isEmpty "hi!" == Bool.false
## expect Str.isEmpty "" == Bool.true
## ```
## expect Str.isEmpty "hi!" == Bool.false
## expect Str.isEmpty "" == Bool.true
## ```
isEmpty : Str -> Bool
## Concatenates two strings together.
##
## expect Str.concat "ab" "cd" == "abcd"
## expect Str.concat "hello" "" == "hello"
## expect Str.concat "" "" == ""
## ```
## expect Str.concat "ab" "cd" == "abcd"
## expect Str.concat "hello" "" == "hello"
## expect Str.concat "" "" == ""
## ```
concat : Str, Str -> Str
## Returns a string of the specified capacity without any content.
@ -155,9 +155,10 @@ withCapacity : Nat -> Str
## Combines a [List] of strings into a single string, with a separator
## string in between each.
##
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
## ```
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
## ```
joinWith : List Str, Str -> Str
## Split a string around a separator.
@ -165,20 +166,22 @@ joinWith : List Str, Str -> Str
## Passing `""` for the separator is not useful;
## it returns the original string wrapped in a [List]. To split a string
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
##
## expect Str.split "1,2,3" "," == ["1","2","3"]
## expect Str.split "1,2,3" "" == ["1,2,3"]
## ```
## expect Str.split "1,2,3" "," == ["1","2","3"]
## expect Str.split "1,2,3" "" == ["1,2,3"]
## ```
split : Str, Str -> List Str
## Repeats a string the given number of times.
##
## expect Str.repeat "z" 3 == "zzz"
## expect Str.repeat "na" 8 == "nananananananana"
##
## ```
## expect Str.repeat "z" 3 == "zzz"
## expect Str.repeat "na" 8 == "nananananananana"
## ```
## Returns `""` when given `""` for the string or `0` for the count.
##
## expect Str.repeat "" 10 == ""
## expect Str.repeat "anything" 0 == ""
## ```
## expect Str.repeat "" 10 == ""
## expect Str.repeat "anything" 0 == ""
## ```
repeat : Str, Nat -> Str
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
@ -186,11 +189,11 @@ repeat : Str, Nat -> Str
##
## Note that the number of extended grapheme clusters can be different from the number
## of visual glyphs rendered! Consider the following examples:
##
## expect Str.countGraphemes "Roc" == 3
## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4
## expect Str.countGraphemes "🕊" == 1
##
## ```
## expect Str.countGraphemes "Roc" == 3
## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4
## expect Str.countGraphemes "🕊" == 1
## ```
## Note that "👩‍👩‍👦‍👦" takes up 4 graphemes (even though visually it appears as a single
## glyph) because under the hood it's represented using an emoji modifier sequence.
## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
@ -205,12 +208,15 @@ graphemes : Str -> List Str
##
## If the given string is empty, or if the given [U32] is not a valid
## code point, returns [Bool.false].
## ```
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
## expect !Str.startsWithScalar "" 40527
## ```
##
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
## expect !Str.startsWithScalar "" 40527
## ## Performance Details
##
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
@ -225,36 +231,39 @@ startsWithScalar : Str, U32 -> Bool
##
## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
##
## expect Str.toScalars "Roc" == [82, 111, 99]
## expect Str.toScalars "鹏" == [40527]
## expect Str.toScalars "சி" == [2970, 3007]
## expect Str.toScalars "🐦" == [128038]
## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
## expect Str.toScalars "" == []
## ```
## expect Str.toScalars "Roc" == [82, 111, 99]
## expect Str.toScalars "鹏" == [40527]
## expect Str.toScalars "சி" == [2970, 3007]
## expect Str.toScalars "🐦" == [128038]
## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
## expect Str.toScalars "" == []
## ```
toScalars : Str -> List U32
## Returns 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].)
##
## expect Str.toUtf8 "Roc" == [82, 111, 99]
## expect Str.toUtf8 "鹏" == [233, 185, 143]
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
## ```
## expect Str.toUtf8 "Roc" == [82, 111, 99]
## expect Str.toUtf8 "鹏" == [233, 185, 143]
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
## ```
toUtf8 : Str -> List U8
## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string.
##
## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`.
##
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
## expect Str.fromUtf8 [] == Ok ""
## expect Str.fromUtf8 [255] |> Result.isErr
## ```
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
## expect Str.fromUtf8 [] == Ok ""
## expect Str.fromUtf8 [255] |> Result.isErr
## ```
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
@ -266,8 +275,9 @@ fromUtf8 = \bytes ->
## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## into a [Str]
##
## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
## ```
## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
## ```
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]
fromUtf8Range = \bytes, config ->
if config.start + config.count <= List.len bytes then
@ -290,57 +300,65 @@ FromUtf8Result : {
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
## Check if the given [Str] starts with a value.
##
## expect Str.startsWith "ABC" "A" == Bool.true
## expect Str.startsWith "ABC" "X" == Bool.false
## ```
## expect Str.startsWith "ABC" "A" == Bool.true
## expect Str.startsWith "ABC" "X" == Bool.false
## ```
startsWith : Str, Str -> Bool
## Check if the given [Str] ends with a value.
##
## expect Str.endsWith "ABC" "C" == Bool.true
## expect Str.endsWith "ABC" "X" == Bool.false
## ```
## expect Str.endsWith "ABC" "C" == Bool.true
## expect Str.endsWith "ABC" "X" == Bool.false
## ```
endsWith : Str, Str -> Bool
## Return the [Str] with all whitespace removed from both the beginning
## as well as the end.
##
## expect Str.trim " Hello \n\n" == "Hello"
## ```
## expect Str.trim " Hello \n\n" == "Hello"
## ```
trim : Str -> Str
## Return the [Str] with all whitespace removed from the beginning.
##
## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
## ```
## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
## ```
trimLeft : Str -> Str
## Return the [Str] with all whitespace removed from the end.
##
## expect Str.trimRight " Hello \n\n" == " Hello"
## ```
## expect Str.trimRight " Hello \n\n" == " Hello"
## ```
trimRight : Str -> Str
## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal
## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
##
## expect Str.toDec "10" == Ok 10dec
## expect Str.toDec "-0.25" == Ok -0.25dec
## expect Str.toDec "not a number" == Err InvalidNumStr
## ```
## expect Str.toDec "10" == Ok 10dec
## expect Str.toDec "-0.25" == Ok -0.25dec
## expect Str.toDec "not a number" == Err InvalidNumStr
## ```
toDec : Str -> Result Dec [InvalidNumStr]
toDec = \string -> strToNumHelp string
## Encode a [Str] to a [F64]. A [F64] value is a 64-bit
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
## specified with a `f64` suffix.
##
## expect Str.toF64 "0.10" == Ok 0.10f64
## expect Str.toF64 "not a number" == Err InvalidNumStr
## ```
## expect Str.toF64 "0.10" == Ok 0.10f64
## expect Str.toF64 "not a number" == Err InvalidNumStr
## ```
toF64 : Str -> Result F64 [InvalidNumStr]
toF64 = \string -> strToNumHelp string
## Encode a [Str] to a [F32].A [F32] value is a 32-bit
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
## specified with a `f32` suffix.
##
## expect Str.toF32 "0.10" == Ok 0.10f32
## expect Str.toF32 "not a number" == Err InvalidNumStr
## ```
## expect Str.toF32 "0.10" == Ok 0.10f32
## expect Str.toF32 "not a number" == Err InvalidNumStr
## ```
toF32 : Str -> Result F32 [InvalidNumStr]
toF32 = \string -> strToNumHelp string
@ -356,20 +374,22 @@ toF32 = \string -> strToNumHelp string
## Calling `Str.toNat "9_000_000_000"` on a 64-bit system will return
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
## hold up to `Num.maxU64`, and 9_000_000_000 is smaller than `Num.maxU64`.
##
## expect Str.toNat "9_000_000_000" == Ok 9000000000
## expect Str.toNat "not a number" == Err InvalidNumStr
## ```
## expect Str.toNat "9_000_000_000" == Ok 9000000000
## expect Str.toNat "not a number" == Err InvalidNumStr
## ```
toNat : Str -> Result Nat [InvalidNumStr]
toNat = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over
## 340 undecillion). It can be specified with a u128 suffix.
##
## expect Str.toU128 "1500" == Ok 1500u128
## expect Str.toU128 "0.1" == Err InvalidNumStr
## expect Str.toU128 "-1" == Err InvalidNumStr
## expect Str.toU128 "not a number" == Err InvalidNumStr
## ```
## expect Str.toU128 "1500" == Ok 1500u128
## expect Str.toU128 "0.1" == Err InvalidNumStr
## expect Str.toU128 "-1" == Err InvalidNumStr
## expect Str.toU128 "not a number" == Err InvalidNumStr
## ```
toU128 : Str -> Result U128 [InvalidNumStr]
toU128 = \string -> strToNumHelp string
@ -377,96 +397,105 @@ toU128 = \string -> strToNumHelp string
## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to
## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified
## with a i128 suffix.
##
## expect Str.toI128 "1500" == Ok 1500i128
## expect Str.toI128 "-1" == Ok -1i128
## expect Str.toI128 "0.1" == Err InvalidNumStr
## expect Str.toI128 "not a number" == Err InvalidNumStr
## ```
## expect Str.toI128 "1500" == Ok 1500i128
## expect Str.toI128 "-1" == Ok -1i128
## expect Str.toI128 "0.1" == Err InvalidNumStr
## expect Str.toI128 "not a number" == Err InvalidNumStr
## ```
toI128 : Str -> Result I128 [InvalidNumStr]
toI128 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers
## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It
## can be specified with a u64 suffix.
##
## expect Str.toU64 "1500" == Ok 1500u64
## expect Str.toU64 "0.1" == Err InvalidNumStr
## expect Str.toU64 "-1" == Err InvalidNumStr
## expect Str.toU64 "not a number" == Err InvalidNumStr
## ```
## expect Str.toU64 "1500" == Ok 1500u64
## expect Str.toU64 "0.1" == Err InvalidNumStr
## expect Str.toU64 "-1" == Err InvalidNumStr
## expect Str.toU64 "not a number" == Err InvalidNumStr
## ```
toU64 : Str -> Result U64 [InvalidNumStr]
toU64 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers
## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be
## specified with a i64 suffix.
##
## expect Str.toI64 "1500" == Ok 1500i64
## expect Str.toI64 "-1" == Ok -1i64
## expect Str.toI64 "0.1" == Err InvalidNumStr
## expect Str.toI64 "not a number" == Err InvalidNumStr
## ```
## expect Str.toI64 "1500" == Ok 1500i64
## expect Str.toI64 "-1" == Ok -1i64
## expect Str.toI64 "0.1" == Err InvalidNumStr
## expect Str.toI64 "not a number" == Err InvalidNumStr
## ```
toI64 : Str -> Result I64 [InvalidNumStr]
toI64 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers
## from `0` to `4_294_967_295` (over 4 billion). It can be specified with
## a u32 suffix.
##
## expect Str.toU32 "1500" == Ok 1500u32
## expect Str.toU32 "0.1" == Err InvalidNumStr
## expect Str.toU32 "-1" == Err InvalidNumStr
## expect Str.toU32 "not a number" == Err InvalidNumStr
## ```
## expect Str.toU32 "1500" == Ok 1500u32
## expect Str.toU32 "0.1" == Err InvalidNumStr
## expect Str.toU32 "-1" == Err InvalidNumStr
## expect Str.toU32 "not a number" == Err InvalidNumStr
## ```
toU32 : Str -> Result U32 [InvalidNumStr]
toU32 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers
## from `-2_147_483_648` to `2_147_483_647`. It can be
## specified with a i32 suffix.
##
## expect Str.toI32 "1500" == Ok 1500i32
## expect Str.toI32 "-1" == Ok -1i32
## expect Str.toI32 "0.1" == Err InvalidNumStr
## expect Str.toI32 "not a number" == Err InvalidNumStr
## ```
## expect Str.toI32 "1500" == Ok 1500i32
## expect Str.toI32 "-1" == Ok -1i32
## expect Str.toI32 "0.1" == Err InvalidNumStr
## expect Str.toI32 "not a number" == Err InvalidNumStr
## ```
toI32 : Str -> Result I32 [InvalidNumStr]
toI32 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers
## from `0` to `65_535`. It can be specified with a u16 suffix.
##
## expect Str.toU16 "1500" == Ok 1500u16
## expect Str.toU16 "0.1" == Err InvalidNumStr
## expect Str.toU16 "-1" == Err InvalidNumStr
## expect Str.toU16 "not a number" == Err InvalidNumStr
## ```
## expect Str.toU16 "1500" == Ok 1500u16
## expect Str.toU16 "0.1" == Err InvalidNumStr
## expect Str.toU16 "-1" == Err InvalidNumStr
## expect Str.toU16 "not a number" == Err InvalidNumStr
## ```
toU16 : Str -> Result U16 [InvalidNumStr]
toU16 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
## from `-32_768` to `32_767`. It can be
## specified with a i16 suffix.
##
## expect Str.toI16 "1500" == Ok 1500i16
## expect Str.toI16 "-1" == Ok -1i16
## expect Str.toI16 "0.1" == Err InvalidNumStr
## expect Str.toI16 "not a number" == Err InvalidNumStr
## ```
## expect Str.toI16 "1500" == Ok 1500i16
## expect Str.toI16 "-1" == Ok -1i16
## expect Str.toI16 "0.1" == Err InvalidNumStr
## expect Str.toI16 "not a number" == Err InvalidNumStr
## ```
toI16 : Str -> Result I16 [InvalidNumStr]
toI16 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers
## from `0` to `255`. It can be specified with a u8 suffix.
##
## expect Str.toU8 "250" == Ok 250u8
## expect Str.toU8 "-0.1" == Err InvalidNumStr
## expect Str.toU8 "not a number" == Err InvalidNumStr
## expect Str.toU8 "1500" == Err InvalidNumStr
## ```
## expect Str.toU8 "250" == Ok 250u8
## expect Str.toU8 "-0.1" == Err InvalidNumStr
## expect Str.toU8 "not a number" == Err InvalidNumStr
## expect Str.toU8 "1500" == Err InvalidNumStr
## ```
toU8 : Str -> Result U8 [InvalidNumStr]
toU8 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
## from `-128` to `127`. It can be
## specified with a i8 suffix.
##
## expect Str.toI8 "-15" == Ok -15i8
## expect Str.toI8 "150.00" == Err InvalidNumStr
## expect Str.toI8 "not a number" == Err InvalidNumStr
## ```
## expect Str.toI8 "-15" == Ok -15i8
## expect Str.toI8 "150.00" == Err InvalidNumStr
## expect Str.toI8 "not a number" == Err InvalidNumStr
## ```
toI8 : Str -> Result I8 [InvalidNumStr]
toI8 = \string -> strToNumHelp string
@ -474,8 +503,9 @@ toI8 = \string -> strToNumHelp string
getUnsafe : Str, Nat -> U8
## Gives the number of bytes in a [Str] value.
##
## expect Str.countUtf8Bytes "Hello World" == 11
## ```
## expect Str.countUtf8Bytes "Hello World" == 11
## ```
countUtf8Bytes : Str -> Nat
## string slice that does not do bounds checking or utf-8 verification
@ -483,9 +513,10 @@ substringUnsafe : Str, Nat, Nat -> Str
## Returns the given [Str] with each occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
##
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
## ```
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
## ```
replaceEach : Str, Str, Str -> Result Str [NotFound]
replaceEach = \haystack, needle, flower ->
when splitFirst haystack needle is
@ -515,9 +546,10 @@ expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
## Returns the given [Str] with the first occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
##
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
## ```
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
## ```
replaceFirst : Str, Str, Str -> Result Str [NotFound]
replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is
@ -530,9 +562,10 @@ expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
## Returns the given [Str] with the last occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
##
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
## ```
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
## ```
replaceLast : Str, Str, Str -> Result Str [NotFound]
replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is
@ -546,9 +579,10 @@ expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
## as the rest of the string after that occurrence.
## Returns [ Err NotFound] if the delimiter is not found.
##
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
## ```
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
## ```
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitFirst = \haystack, needle ->
when firstMatch haystack needle is
@ -599,9 +633,10 @@ firstMatchHelp = \haystack, needle, index, lastPossible ->
## Returns the given [Str] before the last occurrence of a delimiter, as well as
## the rest of the string after that occurrence.
## Returns [Err NotFound] if the delimiter is not found.
##
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
## expect Str.splitLast "no slashes here" "/" == Err NotFound
## ```
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
## expect Str.splitLast "no slashes here" "/" == Err NotFound
## ```
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitLast = \haystack, needle ->
when lastMatch haystack needle is
@ -690,10 +725,11 @@ matchesAtHelp = \state ->
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
## state for each byte. The index for that byte in the string is provided
## to the update function.
##
## f : List U8, U8, Nat -> List U8
## f = \state, byte, _ -> List.append state byte
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
## ```
## f : List U8, U8, Nat -> List U8
## f = \state, byte, _ -> List.append state byte
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
## ```
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
walkUtf8WithIndex = \string, state, step ->
walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string)
@ -711,14 +747,19 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
## Enlarge a string for at least the given number additional bytes.
reserve : Str, Nat -> Str
## Shrink the memory footprint of a str such that it's capacity and length are equal.
## Note: This will also convert seamless slices to regular lists.
releaseExcessCapacity : Str -> Str
## is UB when the scalar is invalid
appendScalarUnsafe : Str, U32 -> Str
## Append a [U32] scalar to the given string. If the given scalar is not a valid
## unicode value, it returns [Err InvalidScalar].
##
## expect Str.appendScalar "H" 105 == Ok "Hi"
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
## ```
## expect Str.appendScalar "H" 105 == Ok "Hi"
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
## ```
appendScalar : Str, U32 -> Result Str [InvalidScalar]
appendScalar = \string, scalar ->
if isValidScalar scalar then
@ -734,10 +775,11 @@ getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
##
## f : List U32, U32 -> List U32
## f = \state, scalar -> List.append state scalar
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
## ```
## f : List U32, U32 -> List U32
## f = \state, scalar -> List.append state scalar
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
## ```
walkScalars : Str, state, (state, U32 -> state) -> state
walkScalars = \string, init, step ->
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
@ -754,16 +796,17 @@ walkScalarsHelp = \string, state, step, index, length ->
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
##
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
## f = \state, scalar ->
## check = 66
## if scalar == check then
## Break [check]
## else
## Continue (List.append state scalar)
## expect Str.walkScalarsUntil "ABC" [] f == [66]
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
## ```
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
## f = \state, scalar ->
## check = 66
## if scalar == check then
## Break [check]
## else
## Continue (List.append state scalar)
## expect Str.walkScalarsUntil "ABC" [] f == [66]
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
## ```
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
walkScalarsUntil = \string, init, step ->
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
@ -795,7 +838,8 @@ strToNumHelp = \string ->
Err InvalidNumStr
## Adds a prefix to the given [Str].
##
## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
## ```
## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
## ```
withPrefix : Str, Str -> Str
withPrefix = \str, prefix -> Str.concat prefix str

View file

@ -1,73 +1,6 @@
use roc_module::symbol::Symbol;
use roc_target::TargetInfo;
use std::ops::Index;
use tempfile::NamedTempFile;
pub const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-wasm32.o"));
// TODO: in the future, we should use Zig's cross-compilation to generate and store these
// for all targets, so that we can do cross-compilation!
#[cfg(unix)]
pub const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-host.o"));
#[cfg(windows)]
pub const HOST_WINDOWS: &[u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/bitcode/builtins-windows-x86_64.obj"
));
pub fn host_wasm_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".wasm")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WASM)?;
Ok(tempfile)
}
#[cfg(unix)]
fn host_unix_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".o")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_UNIX)?;
Ok(tempfile)
}
#[cfg(windows)]
fn host_windows_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".obj")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WINDOWS)?;
Ok(tempfile)
}
pub fn host_tempfile() -> std::io::Result<NamedTempFile> {
#[cfg(unix)]
{
host_unix_tempfile()
}
#[cfg(windows)]
{
host_windows_tempfile()
}
#[cfg(not(any(windows, unix)))]
{
unreachable!()
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct IntrinsicName {
@ -356,8 +289,16 @@ pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.
pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName =
float_intrinsic!("roc_builtins.num.mul_with_overflow");
pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_leading_zero_bits");
pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_trailing_zero_bits");
pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
pub const NUM_BYTES_TO_U64: &str = "roc_builtins.num.bytes_to_u64";
pub const NUM_BYTES_TO_U128: &str = "roc_builtins.num.bytes_to_u128";
pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
@ -392,6 +333,8 @@ pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe";
pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to";
pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity";
pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes";
pub const STR_REFCOUNT_PTR: &str = "roc_builtins.str.refcount_ptr";
pub const STR_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.str.release_excess_capacity";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
@ -409,6 +352,9 @@ pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe";
pub const LIST_RESERVE: &str = "roc_builtins.list.reserve";
pub const LIST_CAPACITY: &str = "roc_builtins.list.capacity";
pub const LIST_REFCOUNT_PTR: &str = "roc_builtins.list.refcount_ptr";
pub const LIST_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.list.release_excess_capacity";
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";

View file

@ -1,28 +1,29 @@
[package]
name = "roc_can"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Canonicalize a roc abstract syntax tree, resolving symbols, re-ordering definitions, and preparing a module for type inference."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_exhaustive = { path = "../exhaustive" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_region = { path = "../region" }
roc_serialize = { path = "../serialize" }
bumpalo.workspace = true
static_assertions.workspace = true
bitvec.workspace = true
roc_types = { path = "../types" }
ven_pretty = { path = "../../vendor/pretty" }
bitvec.workspace = true
bumpalo.workspace = true
static_assertions.workspace = true
[dev-dependencies]
pretty_assertions.workspace = true
indoc.workspace = true
pretty_assertions.workspace = true

View file

@ -448,7 +448,7 @@ pub fn find_type_def_symbols(
As(actual, _, _) => {
stack.push(&actual.value);
}
Tuple { fields: _, ext: _ } => {
Tuple { elems: _, ext: _ } => {
todo!("find_type_def_symbols: Tuple");
}
Record { fields, ext } => {
@ -872,8 +872,41 @@ fn can_annotation_help(
}
}
Tuple { fields: _, ext: _ } => {
todo!("tuple");
Tuple { elems, ext } => {
let (ext_type, is_implicit_openness) = can_extension_type(
env,
pol,
scope,
var_store,
introduced_variables,
local_aliases,
references,
ext,
roc_problem::can::ExtensionTypeKind::Record,
);
debug_assert!(
matches!(is_implicit_openness, ExtImplicitOpenness::No),
"tuples should never be implicitly inferred open"
);
debug_assert!(!elems.is_empty()); // We don't allow empty tuples
let elem_types = can_assigned_tuple_elems(
env,
pol,
&elems.items,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
Type::Tuple(
elem_types,
TypeExtension::from_type(ext_type, is_implicit_openness),
)
}
Record { fields, ext } => {
let (ext_type, is_implicit_openness) = can_extension_type(
@ -1440,6 +1473,39 @@ fn can_assigned_fields<'a>(
field_types
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_assigned_tuple_elems<'a>(
env: &mut Env,
pol: CanPolarity,
elems: &&[Loc<TypeAnnotation<'a>>],
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
) -> VecMap<usize, Type> {
let mut elem_types = VecMap::with_capacity(elems.len());
for (index, loc_elem) in elems.iter().enumerate() {
let elem_type = can_annotation_help(
env,
pol,
&loc_elem.value,
loc_elem.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
elem_types.insert(index, elem_type);
}
elem_types
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_tags<'a>(

View file

@ -126,6 +126,7 @@ map_symbol_to_lowlevel_and_arity! {
StrGetCapacity; STR_CAPACITY; 1,
StrWithCapacity; STR_WITH_CAPACITY; 1,
StrGraphemes; STR_GRAPHEMES; 1,
StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
ListLen; LIST_LEN; 1,
ListWithCapacity; LIST_WITH_CAPACITY; 1,
@ -145,6 +146,7 @@ map_symbol_to_lowlevel_and_arity! {
ListDropAt; LIST_DROP_AT; 2,
ListSwap; LIST_SWAP; 3,
ListGetCapacity; LIST_CAPACITY; 1,
ListReleaseExcessCapacity; LIST_RELEASE_EXCESS_CAPACITY; 1,
ListGetUnsafe; DICT_LIST_GET_UNSAFE; 2,
@ -187,6 +189,8 @@ map_symbol_to_lowlevel_and_arity! {
NumAsin; NUM_ASIN; 1,
NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2,
NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2,
NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2,
NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2,
NumBitwiseAnd; NUM_BITWISE_AND; 2,
NumBitwiseXor; NUM_BITWISE_XOR; 2,
NumBitwiseOr; NUM_BITWISE_OR; 2,
@ -194,6 +198,9 @@ map_symbol_to_lowlevel_and_arity! {
NumShiftRightBy; NUM_SHIFT_RIGHT; 2,
NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2,
NumToStr; NUM_TO_STR; 1,
NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,

View file

@ -1,10 +1,9 @@
use crate::{
def::Def,
expr::{
ClosureData, Expr, Field, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData,
WhenBranchPattern,
ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranchPattern,
},
pattern::{DestructType, ListPatterns, Pattern, RecordDestruct},
pattern::{DestructType, ListPatterns, Pattern, RecordDestruct, TupleDestruct},
};
use roc_module::{
ident::{Lowercase, TagName},
@ -513,7 +512,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
field: field.clone(),
},
RecordAccessor(RecordAccessorData {
RecordAccessor(StructAccessorData {
name,
function_var,
record_var,
@ -521,7 +520,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
ext_var,
field_var,
field,
}) => RecordAccessor(RecordAccessorData {
}) => RecordAccessor(StructAccessorData {
name: *name,
function_var: sub!(*function_var),
record_var: sub!(*record_var),
@ -545,24 +544,6 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
index: *index,
},
TupleAccessor(TupleAccessorData {
name,
function_var,
tuple_var: record_var,
closure_var,
ext_var,
elem_var: field_var,
index,
}) => TupleAccessor(TupleAccessorData {
name: *name,
function_var: sub!(*function_var),
tuple_var: sub!(*record_var),
closure_var: sub!(*closure_var),
ext_var: sub!(*ext_var),
elem_var: sub!(*field_var),
index: *index,
}),
RecordUpdate {
record_var,
ext_var,
@ -794,6 +775,30 @@ fn deep_copy_pattern_help<C: CopyEnv>(
})
.collect(),
},
TupleDestructure {
whole_var,
ext_var,
destructs,
} => TupleDestructure {
whole_var: sub!(*whole_var),
ext_var: sub!(*ext_var),
destructs: destructs
.iter()
.map(|lrd| {
lrd.map(
|TupleDestruct {
destruct_index: index,
var,
typ: (tyvar, pat),
}: &crate::pattern::TupleDestruct| TupleDestruct {
destruct_index: *index,
var: sub!(*var),
typ: (sub!(*tyvar), pat.map(|p| go_help!(p))),
},
)
})
.collect(),
},
List {
list_var,
elem_var,

View file

@ -5,7 +5,7 @@ use crate::expr::Expr::{self, *};
use crate::expr::{
ClosureData, DeclarationTag, Declarations, FunctionDef, OpaqueWrapFunctionData, WhenBranch,
};
use crate::pattern::{Pattern, RecordDestruct};
use crate::pattern::{Pattern, RecordDestruct, TupleDestruct};
use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -35,7 +35,10 @@ pub fn pretty_print_declarations(c: &Ctx, declarations: &Declarations) -> String
DeclarationTag::Expectation => todo!(),
DeclarationTag::ExpectationFx => todo!(),
DeclarationTag::Destructure(_) => todo!(),
DeclarationTag::MutualRecursion { .. } => todo!(),
DeclarationTag::MutualRecursion { .. } => {
// the defs will be printed next
continue;
}
};
defs.push(def);
@ -123,9 +126,10 @@ fn toplevel_function<'a>(
.append(f.line())
.append(f.text("\\"))
.append(f.intersperse(args, f.text(", ")))
.append(f.text("->"))
.append(f.text(" ->"))
.group()
.append(f.line())
.append(expr(c, EPrec::Free, f, body))
.append(expr(c, EPrec::Free, f, body).group())
.nest(2)
.group()
}
@ -330,12 +334,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
} => expr(c, AppArg, f, &loc_expr.value)
.append(f.text(format!(".{}", field.as_str())))
.group(),
TupleAccess { .. } => todo!(),
TupleAccess {
loc_expr, index, ..
} => expr(c, AppArg, f, &loc_expr.value)
.append(f.text(format!(".{index}")))
.group(),
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
f.text(format!("@{}", opaque_name.as_str(c.interns)))
}
RecordAccessor(_) => todo!(),
TupleAccessor(_) => todo!(),
RecordUpdate {
symbol, updates, ..
} => f
@ -386,7 +393,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
),
Crash { .. } => todo!(),
ZeroArgumentTag { .. } => todo!(),
OpaqueRef { .. } => todo!(),
OpaqueRef { name, argument, .. } => maybe_paren!(
Free,
p,
|| true,
pp_sym(c, f, *name)
.append(f.space())
.append(expr(c, AppArg, f, &argument.1.value))
.group()
),
Dbg { .. } => todo!(),
Expect { .. } => todo!(),
ExpectFx { .. } => todo!(),
@ -505,6 +520,19 @@ fn pattern<'a>(
)
.append(f.text("}"))
.group(),
TupleDestructure { destructs, .. } => f
.text("(")
.append(
f.intersperse(
destructs
.iter()
.map(|l| &l.value)
.map(|TupleDestruct { typ: (_, p), .. }| pattern(c, Free, f, &p.value)),
f.text(", "),
),
)
.append(f.text(")"))
.group(),
List { .. } => todo!(),
NumLiteral(_, n, _, _) | IntLiteral(_, _, n, _, _) | FloatLiteral(_, _, n, _, _) => {
f.text(&**n)

View file

@ -15,7 +15,7 @@ use crate::expr::AnnotatedMark;
use crate::expr::ClosureData;
use crate::expr::Declarations;
use crate::expr::Expr::{self, *};
use crate::expr::RecordAccessorData;
use crate::expr::StructAccessorData;
use crate::expr::{canonicalize_expr, Output, Recursive};
use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern};
use crate::procedure::References;
@ -36,6 +36,7 @@ use roc_parse::ast::AssignedField;
use roc_parse::ast::Defs;
use roc_parse::ast::ExtractSpaces;
use roc_parse::ast::TypeHeader;
use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType;
use roc_problem::can::ShadowKind;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
@ -45,6 +46,7 @@ use roc_types::subs::{VarStore, Variable};
use roc_types::types::AliasCommon;
use roc_types::types::AliasKind;
use roc_types::types::AliasVar;
use roc_types::types::IndexOrField;
use roc_types::types::LambdaSet;
use roc_types::types::MemberImpl;
use roc_types::types::OptAbleType;
@ -1995,6 +1997,16 @@ fn pattern_to_vars_by_symbol(
vars_by_symbol.insert(*opaque, expr_var);
}
TupleDestructure { destructs, .. } => {
for destruct in destructs {
pattern_to_vars_by_symbol(
vars_by_symbol,
&destruct.value.typ.1.value,
destruct.value.typ.0,
);
}
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
@ -2316,19 +2328,23 @@ fn canonicalize_pending_body<'a>(
ident: defined_symbol,
..
},
ast::Expr::RecordAccessorFunction(field),
ast::Expr::AccessorFunction(field),
) => {
let field = match field {
Accessor::RecordField(field) => IndexOrField::Field((*field).into()),
Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()),
};
let (loc_can_expr, can_output) = (
Loc::at(
loc_expr.region,
RecordAccessor(RecordAccessorData {
RecordAccessor(StructAccessorData {
name: *defined_symbol,
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
field_var: var_store.fresh(),
field: (*field).into(),
field,
}),
),
Output::default(),

View file

@ -10,9 +10,10 @@ use roc_module::ident::{Lowercase, TagIdIntType, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{
Content, FlatType, GetSubsSlice, RedundantMark, Subs, SubsFmtContent, Variable,
Content, FlatType, GetSubsSlice, RedundantMark, SortedTagsIterator, Subs, SubsFmtContent,
Variable,
};
use roc_types::types::AliasKind;
use roc_types::types::{gather_tags_unsorted_iter, AliasKind};
pub use roc_exhaustive::Context as ExhaustiveContext;
@ -80,6 +81,8 @@ enum IndexCtor<'a> {
Opaque,
/// Index a record type. The arguments are the types of the record fields.
Record(&'a [Lowercase]),
/// Index a tuple type.
Tuple,
/// Index a guard constructor. The arguments are a faux guard pattern, and then the real
/// pattern being guarded. E.g. `A B if g` becomes Guard { [True, (A B)] }.
Guard,
@ -112,6 +115,7 @@ impl<'a> IndexCtor<'a> {
}
RenderAs::Opaque => Self::Opaque,
RenderAs::Record(fields) => Self::Record(fields),
RenderAs::Tuple => Self::Tuple,
RenderAs::Guard => Self::Guard,
}
}
@ -145,9 +149,7 @@ fn index_var(
var = *structure;
}
Content::Structure(structure) => match structure {
FlatType::Func(_, _, _) | FlatType::FunctionOrTagUnion(_, _, _) => {
return Err(TypeError)
}
FlatType::Func(_, _, _) => return Err(TypeError),
FlatType::Apply(Symbol::LIST_LIST, args) => {
match (subs.get_subs_slice(*args), ctor) {
([elem_var], IndexCtor::List) => {
@ -208,6 +210,19 @@ fn index_var(
let vars = opt_vars.expect("constructor must be known in the indexable type if we are exhautiveness checking");
return Ok(vars);
}
FlatType::FunctionOrTagUnion(tags, _, _) => {
let tag_ctor = match ctor {
IndexCtor::Tag(name) => name,
_ => {
internal_error!("constructor in a tag union must be tag")
}
};
let tags = subs.get_subs_slice(*tags);
debug_assert!(tags.contains(tag_ctor), "constructor must be known in the indexable type if we are exhautiveness checking");
return Ok(vec![]);
}
FlatType::EmptyRecord => {
debug_assert!(matches!(ctor, IndexCtor::Record(..)));
// If there are optional record fields we don't unify them, but we need to
@ -354,6 +369,30 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
SP::KnownCtor(union, tag_id, patterns)
}
TupleDestructure { destructs, .. } => {
let tag_id = TagId(0);
let mut patterns = std::vec::Vec::with_capacity(destructs.len());
for Loc {
value: destruct,
region: _,
} in destructs
{
patterns.push(sketch_pattern(&destruct.typ.1.value));
}
let union = Union {
render_as: RenderAs::Tuple,
alternatives: vec![Ctor {
name: CtorName::Tag(TagName("#Record".into())),
tag_id,
arity: destructs.len(),
}],
};
SP::KnownCtor(union, tag_id, patterns)
}
List {
patterns,
list_var: _,
@ -616,64 +655,80 @@ fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union,
use {Content::*, FlatType::*};
match dealias_tag(subs, content) {
let (sorted_tags, ext) = match dealias_tag(subs, content) {
Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => {
let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext);
let mut num_tags = sorted_tags.len();
// DEVIATION: model openness by attaching a #Open constructor, that can never
// be matched unless there's an `Anything` pattern.
let opt_openness_tag = match subs.get_content_without_compacting(ext.var()) {
FlexVar(_) | RigidVar(_) => {
let openness_tag = TagName(NONEXHAUSIVE_CTOR.into());
num_tags += 1;
Some((openness_tag, &[] as _))
}
Structure(EmptyTagUnion) => None,
// Anything else is erroneous and we ignore
_ => None,
};
// High tag ID if we're out-of-bounds.
let mut my_tag_id = TagId(num_tags as TagIdIntType);
let mut alternatives = Vec::with_capacity(num_tags);
let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter());
let mut index = 0;
for (tag, args) in alternatives_iter {
let is_inhabited = args.iter().all(|v| subs.is_inhabited(*v));
if !is_inhabited {
// This constructor is not material; we don't need to match over it!
continue;
}
let tag_id = TagId(index as TagIdIntType);
index += 1;
if this_tag == &tag {
my_tag_id = tag_id;
}
alternatives.push(Ctor {
name: CtorName::Tag(tag),
tag_id,
arity: args.len(),
(sorted_tags, ext)
}
Structure(FunctionOrTagUnion(tags, _, ext)) => {
let (ext_tags, ext) = gather_tags_unsorted_iter(subs, Default::default(), *ext)
.unwrap_or_else(|_| {
internal_error!("Content is not a tag union: {:?}", subs.dbg(whole_var))
});
let mut all_tags: Vec<(TagName, &[Variable])> = Vec::with_capacity(tags.len());
for tag in subs.get_subs_slice(*tags) {
all_tags.push((tag.clone(), &[]));
}
let union = Union {
alternatives,
render_as: RenderAs::Tag,
};
(union, my_tag_id)
for (tag, vars) in ext_tags {
debug_assert!(vars.is_empty());
all_tags.push((tag.clone(), &[]));
}
(Box::new(all_tags.into_iter()) as SortedTagsIterator, ext)
}
_ => internal_error!(
"Content is not a tag union: {:?}",
SubsFmtContent(content, subs)
),
};
let mut num_tags = sorted_tags.len();
// DEVIATION: model openness by attaching a #Open constructor, that can never
// be matched unless there's an `Anything` pattern.
let opt_openness_tag = match subs.get_content_without_compacting(ext.var()) {
FlexVar(_) | RigidVar(_) => {
let openness_tag = TagName(NONEXHAUSIVE_CTOR.into());
num_tags += 1;
Some((openness_tag, &[] as _))
}
Structure(EmptyTagUnion) => None,
// Anything else is erroneous and we ignore
_ => None,
};
// High tag ID if we're out-of-bounds.
let mut my_tag_id = TagId(num_tags as TagIdIntType);
let mut alternatives = Vec::with_capacity(num_tags);
let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter());
let mut index = 0;
for (tag, args) in alternatives_iter {
let is_inhabited = args.iter().all(|v| subs.is_inhabited(*v));
if !is_inhabited {
// This constructor is not material; we don't need to match over it!
continue;
}
let tag_id = TagId(index as TagIdIntType);
index += 1;
if this_tag == &tag {
my_tag_id = tag_id;
}
alternatives.push(Ctor {
name: CtorName::Tag(tag),
tag_id,
arity: args.len(),
});
}
let union = Union {
alternatives,
render_as: RenderAs::Tag,
};
(union, my_tag_id)
}
pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content {

View file

@ -19,12 +19,13 @@ use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, Defs, StrLiteral};
use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::num::SingleQuoteBound;
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
use roc_types::types::{Alias, Category, IndexOrField, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display};
use std::{char, u32};
@ -186,8 +187,8 @@ pub enum Expr {
field: Lowercase,
},
/// field accessor as a function, e.g. (.foo) expr
RecordAccessor(RecordAccessorData),
/// tuple or field accessor as a function, e.g. (.foo) expr or (.1) expr
RecordAccessor(StructAccessorData),
TupleAccess {
tuple_var: Variable,
@ -197,9 +198,6 @@ pub enum Expr {
index: usize,
},
/// tuple accessor as a function, e.g. (.1) expr
TupleAccessor(TupleAccessorData),
RecordUpdate {
record_var: Variable,
ext_var: Variable,
@ -315,9 +313,8 @@ impl Expr {
Self::Record { .. } => Category::Record,
Self::EmptyRecord => Category::Record,
Self::RecordAccess { field, .. } => Category::RecordAccess(field.clone()),
Self::RecordAccessor(data) => Category::RecordAccessor(data.field.clone()),
Self::RecordAccessor(data) => Category::Accessor(data.field.clone()),
Self::TupleAccess { index, .. } => Category::TupleAccess(*index),
Self::TupleAccessor(data) => Category::TupleAccessor(data.index),
Self::RecordUpdate { .. } => Category::Record,
Self::Tag {
name, arguments, ..
@ -383,43 +380,30 @@ pub struct ClosureData {
pub loc_body: Box<Loc<Expr>>,
}
/// A tuple accessor like `.2`, which is equivalent to `\x -> x.2`
/// TupleAccessors are desugared to closures; they need to have a name
/// A record or tuple accessor like `.foo` or `.0`, which is equivalent to `\r -> r.foo`
/// Struct accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TupleAccessorData {
pub name: Symbol,
pub function_var: Variable,
pub tuple_var: Variable,
pub closure_var: Variable,
pub ext_var: Variable,
pub elem_var: Variable,
pub index: usize,
}
/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
/// RecordAccessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RecordAccessorData {
pub struct StructAccessorData {
pub name: Symbol,
pub function_var: Variable,
pub record_var: Variable,
pub closure_var: Variable,
pub ext_var: Variable,
pub field_var: Variable,
pub field: Lowercase,
/// Note that the `field` field is an `IndexOrField` in order to represent both
/// record and tuple accessors. This is different from `TupleAccess` and
/// `RecordAccess` (and RecordFields/TupleElems), which share less of their implementation.
pub field: IndexOrField,
}
impl RecordAccessorData {
impl StructAccessorData {
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
let RecordAccessorData {
let StructAccessorData {
name,
function_var,
record_var,
@ -436,12 +420,21 @@ impl RecordAccessorData {
// into
//
// (\r -> r.foo)
let body = Expr::RecordAccess {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
field,
let body = match field {
IndexOrField::Index(index) => Expr::TupleAccess {
tuple_var: record_var,
ext_var,
elem_var: field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
index,
},
IndexOrField::Field(field) => Expr::RecordAccess {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
field,
},
};
let loc_body = Loc::at_zero(body);
@ -1080,15 +1073,18 @@ pub fn canonicalize_expr<'a>(
output,
)
}
ast::Expr::RecordAccessorFunction(field) => (
RecordAccessor(RecordAccessorData {
ast::Expr::AccessorFunction(field) => (
RecordAccessor(StructAccessorData {
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
field_var: var_store.fresh(),
field: (*field).into(),
field: match field {
Accessor::RecordField(field) => IndexOrField::Field((*field).into()),
Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()),
},
}),
Output::default(),
),
@ -1106,18 +1102,6 @@ pub fn canonicalize_expr<'a>(
output,
)
}
ast::Expr::TupleAccessorFunction(index) => (
TupleAccessor(TupleAccessorData {
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
tuple_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
elem_var: var_store.fresh(),
index: index.parse().unwrap(),
}),
Output::default(),
),
ast::Expr::Tag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
@ -1874,7 +1858,6 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ RecordAccessor { .. }
| other @ TupleAccessor { .. }
| other @ RecordUpdate { .. }
| other @ Var(..)
| other @ AbilityMember(..)
@ -3004,7 +2987,6 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
| Expr::Str(_)
| Expr::ZeroArgumentTag { .. }
| Expr::RecordAccessor(_)
| Expr::TupleAccessor(_)
| Expr::SingleQuote(..)
| Expr::EmptyRecord
| Expr::TypedHole(_)

View file

@ -837,21 +837,10 @@ fn fix_values_captured_in_closure_defs(
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
no_capture_symbols.extend(
crate::traverse::symbols_introduced_from_pattern(&def.loc_pattern).map(|ls| ls.value),
);
}
for def in defs.iter_mut() {
fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
}
// Mutually recursive functions should both capture the union of all their capture sets
//
// Really unfortunate we make a lot of clones here, can this be done more efficiently?
let mut total_capture_set = Vec::default();
let mut total_capture_set = VecMap::default();
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
@ -860,8 +849,16 @@ fn fix_values_captured_in_closure_defs(
total_capture_set.extend(captured_symbols.iter().copied());
}
}
for def in defs.iter() {
for symbol in
crate::traverse::symbols_introduced_from_pattern(&def.loc_pattern).map(|ls| ls.value)
{
total_capture_set.remove(&symbol);
}
}
let mut total_capture_set: Vec<_> = total_capture_set.into_iter().collect();
total_capture_set.sort_by_key(|(sym, _)| *sym);
total_capture_set.dedup_by_key(|(sym, _)| *sym);
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
@ -870,6 +867,10 @@ fn fix_values_captured_in_closure_defs(
*captured_symbols = total_capture_set.clone();
}
}
for def in defs.iter_mut() {
fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
}
}
fn fix_values_captured_in_closure_pattern(
@ -918,6 +919,15 @@ fn fix_values_captured_in_closure_pattern(
}
}
}
TupleDestructure { destructs, .. } => {
for loc_destruct in destructs.iter_mut() {
fix_values_captured_in_closure_pattern(
&mut loc_destruct.value.typ.1.value,
no_capture_symbols,
closure_captures,
)
}
}
List { patterns, .. } => {
for loc_pat in patterns.patterns.iter_mut() {
fix_values_captured_in_closure_pattern(
@ -1023,9 +1033,9 @@ fn fix_values_captured_in_closure_expr(
captured_symbols.retain(|(s, _)| s != name);
let original_captures_len = captured_symbols.len();
let mut num_visited = 0;
let mut i = 0;
while num_visited < original_captures_len {
let mut added_captures = false;
while i < original_captures_len {
// If we've captured a capturing closure, replace the captured closure symbol with
// the symbols of its captures. That way, we can construct the closure with the
// captures it needs inside our body.
@ -1039,19 +1049,21 @@ fn fix_values_captured_in_closure_expr(
let (captured_symbol, _) = captured_symbols[i];
if let Some(captures) = closure_captures.get(&captured_symbol) {
debug_assert!(!captures.is_empty());
captured_symbols.swap_remove(i);
captured_symbols.extend(captures);
captured_symbols.swap_remove(i);
// Jump two, because the next element is now one of the newly-added captures,
// which we don't need to check.
i += 2;
added_captures = true;
} else {
i += 1;
}
num_visited += 1;
}
if captured_symbols.len() > original_captures_len {
if added_captures {
// Re-sort, since we've added new captures.
captured_symbols.sort_by_key(|(sym, _)| *sym);
captured_symbols.dedup_by_key(|(sym, _)| *sym);
}
if captured_symbols.is_empty() {
@ -1087,8 +1099,7 @@ fn fix_values_captured_in_closure_expr(
| TypedHole { .. }
| RuntimeError(_)
| ZeroArgumentTag { .. }
| RecordAccessor { .. }
| TupleAccessor { .. } => {}
| RecordAccessor { .. } => {}
List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() {

View file

@ -130,8 +130,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| RecordAccessorFunction(_)
| TupleAccessorFunction(_)
| AccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)

View file

@ -58,6 +58,11 @@ pub enum Pattern {
ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>,
},
TupleDestructure {
whole_var: Variable,
ext_var: Variable,
destructs: Vec<Loc<TupleDestruct>>,
},
List {
list_var: Variable,
elem_var: Variable,
@ -100,6 +105,7 @@ impl Pattern {
AppliedTag { whole_var, .. } => Some(*whole_var),
UnwrappedOpaque { whole_var, .. } => Some(*whole_var),
RecordDestructure { whole_var, .. } => Some(*whole_var),
TupleDestructure { whole_var, .. } => Some(*whole_var),
List {
list_var: whole_var,
..
@ -130,7 +136,21 @@ impl Pattern {
| UnsupportedPattern(..)
| MalformedPattern(..)
| AbilityMemberSpecialization { .. } => true,
RecordDestructure { destructs, .. } => destructs.is_empty(),
RecordDestructure { destructs, .. } => {
// If all destructs are surely exhaustive, then this is surely exhaustive.
destructs.iter().all(|d| match &d.value.typ {
DestructType::Required | DestructType::Optional(_, _) => false,
DestructType::Guard(_, pat) => pat.value.surely_exhaustive(),
})
}
TupleDestructure { destructs, .. } => {
// If all destructs are surely exhaustive, then this is surely exhaustive.
destructs
.iter()
.all(|d| d.value.typ.1.value.surely_exhaustive())
}
As(pattern, _identifier) => pattern.value.surely_exhaustive(),
List { patterns, .. } => patterns.surely_exhaustive(),
AppliedTag { .. }
@ -160,6 +180,7 @@ impl Pattern {
UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque),
RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord,
RecordDestructure { .. } => C::Record,
TupleDestructure { .. } => C::Tuple,
List { .. } => C::List,
NumLiteral(..) => C::Num,
IntLiteral(..) => C::Int,
@ -215,6 +236,13 @@ pub struct RecordDestruct {
pub typ: DestructType,
}
#[derive(Clone, Debug)]
pub struct TupleDestruct {
pub var: Variable,
pub destruct_index: usize,
pub typ: (Variable, Loc<Pattern>),
}
#[derive(Clone, Debug)]
pub enum DestructType {
Required,
@ -554,8 +582,38 @@ pub fn canonicalize_pattern<'a>(
)
}
Tuple(_patterns) => {
todo!("canonicalize_pattern: Tuple")
Tuple(patterns) => {
let ext_var = var_store.fresh();
let whole_var = var_store.fresh();
let mut destructs = Vec::with_capacity(patterns.len());
for (i, loc_pattern) in patterns.iter().enumerate() {
let can_guard = canonicalize_pattern(
env,
var_store,
scope,
output,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
permit_shadows,
);
destructs.push(Loc {
region: loc_pattern.region,
value: TupleDestruct {
destruct_index: i,
var: var_store.fresh(),
typ: (var_store.fresh(), can_guard),
},
});
}
Pattern::TupleDestructure {
whole_var,
ext_var,
destructs,
}
}
RecordDestructure(patterns) => {
@ -861,7 +919,8 @@ pub enum BindingsFromPattern<'a> {
pub enum BindingsFromPatternWork<'a> {
Pattern(&'a Loc<Pattern>),
Destruct(&'a Loc<RecordDestruct>),
RecordDestruct(&'a Loc<RecordDestruct>),
TupleDestruct(&'a Loc<TupleDestruct>),
}
impl<'a> BindingsFromPattern<'a> {
@ -911,8 +970,12 @@ impl<'a> BindingsFromPattern<'a> {
let (_, loc_arg) = &**argument;
stack.push(Pattern(loc_arg));
}
TupleDestructure { destructs, .. } => {
let it = destructs.iter().rev().map(TupleDestruct);
stack.extend(it);
}
RecordDestructure { destructs, .. } => {
let it = destructs.iter().rev().map(Destruct);
let it = destructs.iter().rev().map(RecordDestruct);
stack.extend(it);
}
NumLiteral(..)
@ -930,7 +993,7 @@ impl<'a> BindingsFromPattern<'a> {
}
}
}
BindingsFromPatternWork::Destruct(loc_destruct) => {
BindingsFromPatternWork::RecordDestruct(loc_destruct) => {
match &loc_destruct.value.typ {
DestructType::Required | DestructType::Optional(_, _) => {
return Some((loc_destruct.value.symbol, loc_destruct.region));
@ -941,6 +1004,10 @@ impl<'a> BindingsFromPattern<'a> {
}
}
}
BindingsFromPatternWork::TupleDestruct(loc_destruct) => {
let inner = &loc_destruct.value.typ.1;
stack.push(BindingsFromPatternWork::Pattern(inner))
}
}
}

View file

@ -9,9 +9,9 @@ use crate::{
def::{Annotation, Declaration, Def},
expr::{
self, AnnotatedMark, ClosureData, Declarations, Expr, Field, OpaqueWrapFunctionData,
RecordAccessorData, TupleAccessorData,
StructAccessorData,
},
pattern::{DestructType, Pattern, RecordDestruct},
pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
};
macro_rules! visit_list {
@ -242,7 +242,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
record_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
Expr::RecordAccessor(RecordAccessorData { .. }) => { /* terminal */ }
Expr::RecordAccessor(StructAccessorData { .. }) => { /* terminal */ }
Expr::TupleAccess {
elem_var,
loc_expr,
@ -250,7 +250,6 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
tuple_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *elem_var),
Expr::TupleAccessor(TupleAccessorData { .. }) => { /* terminal */ }
Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ }
Expr::RecordUpdate {
record_var: _,
@ -483,6 +482,16 @@ pub trait Visitor: Sized {
walk_record_destruct(self, destruct);
}
}
fn visit_tuple_destruct(&mut self, destruct: &TupleDestruct, region: Region) {
if self.should_visit(region) {
self.visit_pattern(
&destruct.typ.1.value,
destruct.typ.1.region,
Some(destruct.typ.0),
)
}
}
}
pub fn walk_pattern<V: Visitor>(visitor: &mut V, pattern: &Pattern) {
@ -503,6 +512,9 @@ pub fn walk_pattern<V: Visitor>(visitor: &mut V, pattern: &Pattern) {
RecordDestructure { destructs, .. } => destructs
.iter()
.for_each(|d| visitor.visit_record_destruct(&d.value, d.region)),
TupleDestructure { destructs, .. } => destructs
.iter()
.for_each(|d| visitor.visit_tuple_destruct(&d.value, d.region)),
List {
patterns, elem_var, ..
} => patterns

View file

@ -1,16 +1,17 @@
[package]
name = "roc_collections"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Domain-specific collections created for the needs of the compiler."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
fnv.workspace = true
im.workspace = true
im-rc.workspace = true
wyhash.workspace = true
bumpalo.workspace = true
hashbrown.workspace = true
bitvec.workspace = true
bumpalo.workspace = true
fnv.workspace = true
hashbrown.workspace = true
im-rc.workspace = true
im.workspace = true
wyhash.workspace = true

View file

@ -1,18 +1,20 @@
[package]
name = "roc_constrain"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Responsible for building the set of constraints that are used during type inference of a program, and for gathering context needed for pleasant error messages when a type error occurs."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
arrayvec = "0.7.2"
arrayvec.workspace = true

View file

@ -17,7 +17,7 @@ use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{
AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, ExpectLookup, Field,
FunctionDef, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData, WhenBranch,
FunctionDef, OpaqueWrapFunctionData, StructAccessorData, WhenBranch,
};
use roc_can::pattern::Pattern;
use roc_can::traverse::symbols_introduced_from_pattern;
@ -30,7 +30,7 @@ use roc_region::all::{Loc, Region};
use roc_types::subs::{IllegalCycleMark, Variable};
use roc_types::types::Type::{self, *};
use roc_types::types::{
AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField,
AliasKind, AnnotationSource, Category, IndexOrField, OptAbleType, PReason, Reason, RecordField,
TypeExtension, TypeTag, Types,
};
@ -1162,7 +1162,7 @@ pub fn constrain_expr(
[constraint, eq, record_con],
)
}
RecordAccessor(RecordAccessorData {
RecordAccessor(StructAccessorData {
name: closure_name,
function_var,
field,
@ -1176,19 +1176,32 @@ pub fn constrain_expr(
let field_var = *field_var;
let field_type = Variable(field_var);
let mut field_types = SendMap::default();
let label = field.clone();
field_types.insert(label, RecordField::Demanded(field_type.clone()));
let record_type = Type::Record(
field_types,
TypeExtension::from_non_annotation_type(ext_type),
);
let record_type = match field {
IndexOrField::Field(field) => {
let mut field_types = SendMap::default();
let label = field.clone();
field_types.insert(label, RecordField::Demanded(field_type.clone()));
Type::Record(
field_types,
TypeExtension::from_non_annotation_type(ext_type),
)
}
IndexOrField::Index(index) => {
let mut field_types = VecMap::with_capacity(1);
field_types.insert(*index, field_type.clone());
Type::Tuple(
field_types,
TypeExtension::from_non_annotation_type(ext_type),
)
}
};
let record_type_index = {
let typ = types.from_old_type(&record_type);
constraints.push_type(types, typ)
};
let category = Category::RecordAccessor(field.clone());
let category = Category::Accessor(field.clone());
let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
let record_con =
@ -1288,88 +1301,6 @@ pub fn constrain_expr(
let eq = constraints.equal_types_var(elem_var, expected, category, region);
constraints.exists_many([*tuple_var, elem_var, ext_var], [constraint, eq, tuple_con])
}
TupleAccessor(TupleAccessorData {
name: closure_name,
function_var,
tuple_var,
closure_var,
ext_var,
elem_var,
index,
}) => {
let ext_var = *ext_var;
let ext_type = Variable(ext_var);
let elem_var = *elem_var;
let elem_type = Variable(elem_var);
let mut elem_types = VecMap::with_capacity(1);
elem_types.insert(*index, elem_type.clone());
let record_type = Type::Tuple(
elem_types,
TypeExtension::from_non_annotation_type(ext_type),
);
let record_type_index = {
let typ = types.from_old_type(&record_type);
constraints.push_type(types, typ)
};
let category = Category::TupleAccessor(*index);
let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
let record_con =
constraints.equal_types_var(*tuple_var, record_expected, category.clone(), region);
let expected_lambda_set = {
let lambda_set_ty = {
let typ = types.from_old_type(&Type::ClosureTag {
name: *closure_name,
captures: vec![],
ambient_function: *function_var,
});
constraints.push_type(types, typ)
};
constraints.push_expected_type(NoExpectation(lambda_set_ty))
};
let closure_type = Type::Variable(*closure_var);
let function_type_index = {
let typ = types.from_old_type(&Type::Function(
vec![record_type],
Box::new(closure_type),
Box::new(elem_type),
));
constraints.push_type(types, typ)
};
let cons = [
constraints.equal_types_var(
*closure_var,
expected_lambda_set,
category.clone(),
region,
),
constraints.equal_types(function_type_index, expected, category.clone(), region),
{
let store_fn_var_index = constraints.push_variable(*function_var);
let store_fn_var_expected =
constraints.push_expected_type(NoExpectation(store_fn_var_index));
constraints.equal_types(
function_type_index,
store_fn_var_expected,
category,
region,
)
},
record_con,
];
constraints.exists_many(
[*tuple_var, *function_var, *closure_var, elem_var, ext_var],
cons,
)
}
LetRec(defs, loc_ret, cycle_mark) => {
let body_con = constrain_expr(
types,
@ -4001,10 +3932,6 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
// RecordAccessor functions `.field` are equivalent to closures `\r -> r.field`, no need to weaken them.
return true;
}
TupleAccessor(_) => {
// TupleAccessor functions `.0` are equivalent to closures `\r -> r.0`, no need to weaken them.
return true;
}
OpaqueWrapFunction(_) => {
// Opaque wrapper functions `@Q` are equivalent to closures `\x -> @Q x`, no need to weaken them.
return true;

View file

@ -3,7 +3,7 @@ use crate::expr::{constrain_expr, Env};
use roc_can::constraint::{Constraint, Constraints, PExpectedTypeIndex, TypeOrVar};
use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct};
use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct, TupleDestruct};
use roc_collections::all::{HumanIndex, SendMap};
use roc_collections::VecMap;
use roc_module::ident::Lowercase;
@ -125,6 +125,10 @@ fn headers_from_annotation_help(
_ => false,
},
TupleDestructure { destructs: _, .. } => {
todo!();
}
List { patterns, .. } => {
if let Some((_, Some(rest))) = patterns.opt_rest {
let annotation_index = {
@ -465,6 +469,96 @@ pub fn constrain_pattern(
));
}
TupleDestructure {
whole_var,
ext_var,
destructs,
} => {
state.vars.push(*whole_var);
state.vars.push(*ext_var);
let ext_type = Type::Variable(*ext_var);
let mut elem_types: VecMap<usize, Type> = VecMap::default();
for Loc {
value:
TupleDestruct {
destruct_index: index,
var,
typ,
},
..
} in destructs.iter()
{
let pat_type = Type::Variable(*var);
let pat_type_index = constraints.push_variable(*var);
let expected =
constraints.push_pat_expected_type(PExpected::NoExpectation(pat_type_index));
let (guard_var, loc_guard) = typ;
let elem_type = {
let guard_type = constraints.push_variable(*guard_var);
let expected_pat = constraints.push_pat_expected_type(PExpected::ForReason(
PReason::PatternGuard,
pat_type_index,
loc_guard.region,
));
state.constraints.push(constraints.pattern_presence(
guard_type,
expected_pat,
PatternCategory::PatternGuard,
region,
));
state.vars.push(*guard_var);
constrain_pattern(
types,
constraints,
env,
&loc_guard.value,
loc_guard.region,
expected,
state,
);
pat_type
};
elem_types.insert(*index, elem_type);
state.vars.push(*var);
}
let tuple_type = {
let typ = types.from_old_type(&Type::Tuple(
elem_types,
TypeExtension::from_non_annotation_type(ext_type),
));
constraints.push_type(types, typ)
};
let whole_var_index = constraints.push_variable(*whole_var);
let expected_record =
constraints.push_expected_type(Expected::NoExpectation(tuple_type));
let whole_con = constraints.equal_types(
whole_var_index,
expected_record,
Category::Storage(std::file!(), std::line!()),
region,
);
let record_con = constraints.pattern_presence(
whole_var_index,
expected,
PatternCategory::Record,
region,
);
state.constraints.push(whole_con);
state.constraints.push(record_con);
}
RecordDestructure {
whole_var,
ext_var,

View file

@ -1,9 +1,10 @@
[package]
name = "roc_debug_flags"
version = "0.0.1"
edition = "2021"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
description = "Environment variables that can be toggled to aid debugging of the compiler itself."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]

View file

@ -1,25 +1,27 @@
[package]
name = "roc_derive"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides auto-derivers for builtin abilities like `Hash` and `Decode`."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_derive_key = { path = "../derive_key" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_derive_key = { path = "../derive_key" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
bumpalo.workspace = true
[features]
default = []
debug-derived-symbols = ["roc_module/debug-symbols"]
default = []
# Enables open extension variables for constructed records and tag unions.
# This is not necessary for code generation, but may be necessary if you are
# constraining and solving generated derived bodies.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,104 @@
use roc_can::expr::Expr;
use roc_error_macros::internal_error;
use roc_module::called_via::CalledVia;
use roc_module::symbol::Symbol;
use roc_region::all::Loc;
use roc_types::subs::{Content, FlatType, GetSubsSlice, SubsSlice, Variable};
use roc_types::types::AliasKind;
use crate::decoding::wrap_in_decode_custom_decode_with;
use crate::synth_var;
use crate::util::Env;
pub(crate) fn decoder(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) {
// Build
//
// def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting
// def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
//
// NB: reduction to `Decode.list Decode.decoder` is not possible to the HRR.
use Expr::*;
// Decode.list Decode.decoder : Decoder (List elem) fmt
let (decode_list_call, this_decode_list_ret_var) = {
// List elem
let elem_var = env.subs.fresh_unnamed_flex_var();
// Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting
let (elem_decoder, elem_decoder_var) = {
// build `Decode.decoder : Decoder elem fmt` type
// Decoder val fmt | val has Decoding, fmt has EncoderFormatting
let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
// set val ~ elem
let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) {
Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque)
if vars.type_variables_len == 2 =>
{
env.subs.get_subs_slice(vars.type_variables())[0]
}
_ => internal_error!("Decode.decode not an opaque type"),
};
env.unify(val_var, elem_var);
(
AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var),
elem_decoder_var,
)
};
// Build `Decode.list Decode.decoder` type
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST);
// Decoder elem fmt -a-> b
let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]);
let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var();
let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var();
let this_decode_list_fn_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(
elem_decoder_var_slice,
this_decode_list_clos_var,
this_decode_list_ret_var,
)),
);
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
// ~ Decoder elem fmt -a -> b
env.unify(decode_list_fn_var, this_decode_list_fn_var);
let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var);
let decode_list_fn = Box::new((
decode_list_fn_var,
Loc::at_zero(decode_list_member),
this_decode_list_clos_var,
this_decode_list_ret_var,
));
let decode_list_call = Call(
decode_list_fn,
vec![(elem_decoder_var, Loc::at_zero(elem_decoder))],
CalledVia::Space,
);
(decode_list_call, this_decode_list_ret_var)
};
let bytes_sym = env.new_symbol("bytes");
let fmt_sym = env.new_symbol("fmt");
let fmt_var = env.subs.fresh_unnamed_flex_var();
let captures = vec![];
wrap_in_decode_custom_decode_with(
env,
bytes_sym,
(fmt_sym, fmt_var),
captures,
(decode_list_call, this_decode_list_ret_var),
)
}

View file

@ -0,0 +1,990 @@
use roc_can::expr::{
AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch, WhenBranchPattern,
};
use roc_can::pattern::Pattern;
use roc_collections::SendMap;
use roc_module::called_via::CalledVia;
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{
Content, ExhaustiveMark, FlatType, LambdaSet, OptVariable, RecordFields, RedundantMark,
SubsSlice, TagExt, UnionLambdas, UnionTags, Variable,
};
use roc_types::types::RecordField;
use crate::synth_var;
use crate::util::{Env, ExtensionKind};
use super::wrap_in_decode_custom_decode_with;
/// Implements decoding of a record. For example, for
///
/// ```text
/// {first: a, second: b}
/// ```
///
/// we'd like to generate an impl like
///
/// ```roc
/// decoder : Decoder {first: a, second: b} fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting
/// decoder =
/// initialState : {f0: Result a [NoField], f1: Result b [NoField]}
/// initialState = {f0: Err NoField, f1: Err NoField}
///
/// stepField = \state, field ->
/// when field is
/// "first" ->
/// Keep (Decode.custom \bytes, fmt ->
/// when Decode.decodeWith bytes Decode.decoder fmt is
/// {result, rest} ->
/// {result: Result.map result \val -> {state & f0: Ok val}, rest})
/// "second" ->
/// Keep (Decode.custom \bytes, fmt ->
/// when Decode.decodeWith bytes Decode.decoder fmt is
/// {result, rest} ->
/// {result: Result.map result \val -> {state & f1: Ok val}, rest})
/// _ -> Skip
///
/// finalizer = \{f0, f1} ->
/// when f0 is
/// Ok first ->
/// when f1 is
/// Ok second -> Ok {first, second}
/// Err NoField -> Err TooShort
/// Err NoField -> Err TooShort
///
/// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.record initialState stepField finalizer) fmt
/// ```
pub(crate) fn decoder(
env: &mut Env,
_def_symbol: Symbol,
fields: Vec<Lowercase>,
) -> (Expr, Variable) {
// The decoded type of each field in the record, e.g. {first: a, second: b}.
let mut field_vars = Vec::with_capacity(fields.len());
// The type of each field in the decoding state, e.g. {first: Result a [NoField], second: Result b [NoField]}
let mut result_field_vars = Vec::with_capacity(fields.len());
// initialState = ...
let (initial_state_var, initial_state) =
initial_state(env, &fields, &mut field_vars, &mut result_field_vars);
// finalizer = ...
let (finalizer, finalizer_var, decode_err_var) = finalizer(
env,
initial_state_var,
&fields,
&field_vars,
&result_field_vars,
);
// stepField = ...
let (step_field, step_var) = step_field(
env,
fields,
&field_vars,
&result_field_vars,
initial_state_var,
decode_err_var,
);
// Build up the type of `Decode.record` we expect
let record_decoder_var = env.subs.fresh_unnamed_flex_var();
let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var();
let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_RECORD);
let this_decode_record_var = {
let flat_type = FlatType::Func(
SubsSlice::insert_into_subs(env.subs, [initial_state_var, step_var, finalizer_var]),
decode_record_lambda_set,
record_decoder_var,
);
synth_var(env.subs, Content::Structure(flat_type))
};
env.unify(decode_record_var, this_decode_record_var);
// Decode.record initialState stepField finalizer
let call_decode_record = Expr::Call(
Box::new((
this_decode_record_var,
Loc::at_zero(Expr::AbilityMember(
Symbol::DECODE_RECORD,
None,
this_decode_record_var,
)),
decode_record_lambda_set,
record_decoder_var,
)),
vec![
(initial_state_var, Loc::at_zero(initial_state)),
(step_var, Loc::at_zero(step_field)),
(finalizer_var, Loc::at_zero(finalizer)),
],
CalledVia::Space,
);
let (call_decode_custom, decode_custom_ret_var) = {
let bytes_sym = env.new_symbol("bytes");
let fmt_sym = env.new_symbol("fmt");
let fmt_var = env.subs.fresh_unnamed_flex_var();
let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with(
env,
bytes_sym,
(fmt_sym, fmt_var),
vec![],
(call_decode_record, record_decoder_var),
);
(decode_custom, decode_custom_var)
};
(call_decode_custom, decode_custom_ret_var)
}
// Example:
// stepField = \state, field ->
// when field is
// "first" ->
// Keep (Decode.custom \bytes, fmt ->
// # Uses a single-branch `when` because `let` is more expensive to monomorphize
// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
// })
//
// "second" ->
// Keep (Decode.custom \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & second: Ok val},
// Err err -> Err err
// })
//
// _ -> Skip
fn step_field(
env: &mut Env,
fields: Vec<Lowercase>,
field_vars: &[Variable],
result_field_vars: &[Variable],
state_record_var: Variable,
decode_err_var: Variable,
) -> (Expr, Variable) {
let state_arg_symbol = env.new_symbol("stateRecord");
let field_arg_symbol = env.new_symbol("field");
// +1 because of the default branch.
let mut branches = Vec::with_capacity(fields.len() + 1);
let keep_payload_var = env.subs.fresh_unnamed_flex_var();
let keep_or_skip_var = {
let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]);
let flat_type = FlatType::TagUnion(
UnionTags::insert_slices_into_subs(
env.subs,
[
("Keep".into(), keep_payload_subs_slice),
("Skip".into(), Default::default()),
],
),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(flat_type))
};
for ((field_name, &field_var), &result_field_var) in fields
.into_iter()
.zip(field_vars.iter())
.zip(result_field_vars.iter())
{
// Example:
// "first" ->
// Keep (Decode.custom \bytes, fmt ->
// # Uses a single-branch `when` because `let` is more expensive to monomorphize
// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
// }
// )
let this_custom_callback_var;
let custom_callback_ret_var;
let custom_callback = {
// \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
// }
let bytes_arg_symbol = env.new_symbol("bytes");
let fmt_arg_symbol = env.new_symbol("fmt");
let bytes_arg_var = env.subs.fresh_unnamed_flex_var();
let fmt_arg_var = env.subs.fresh_unnamed_flex_var();
// rec.result : [Ok field_var, Err DecodeError]
let rec_dot_result = {
let tag_union = FlatType::TagUnion(
UnionTags::for_result(env.subs, field_var, decode_err_var),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(tag_union))
};
// rec : { rest: List U8, result: (typeof rec.result) }
let rec_var = {
let fields = RecordFields::insert_into_subs(
env.subs,
[
("rest".into(), RecordField::Required(Variable::LIST_U8)),
("result".into(), RecordField::Required(rec_dot_result)),
],
);
let record = FlatType::Record(fields, Variable::EMPTY_RECORD);
synth_var(env.subs, Content::Structure(record))
};
// `Decode.decoder` for the field's value
let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
let lambda_set_var = env.subs.fresh_unnamed_flex_var();
let this_decode_with_var = {
let subs_slice = SubsSlice::insert_into_subs(
env.subs,
[bytes_arg_var, decoder_var, fmt_arg_var],
);
let this_decode_with_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)),
);
env.unify(decode_with_var, this_decode_with_var);
this_decode_with_var
};
// The result of decoding this field's value - either the updated state, or a decoding error.
let when_expr_var = {
let flat_type = FlatType::TagUnion(
UnionTags::for_result(env.subs, state_record_var, decode_err_var),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(flat_type))
};
// What our decoder passed to `Decode.custom` returns - the result of decoding the
// field's value, and the remaining bytes.
custom_callback_ret_var = {
let rest_field = RecordField::Required(Variable::LIST_U8);
let result_field = RecordField::Required(when_expr_var);
let flat_type = FlatType::Record(
RecordFields::insert_into_subs(
env.subs,
[("rest".into(), rest_field), ("result".into(), result_field)],
),
Variable::EMPTY_RECORD,
);
synth_var(env.subs, Content::Structure(flat_type))
};
let custom_callback_body = {
let rec_symbol = env.new_symbol("rec");
// # Uses a single-branch `when` because `let` is more expensive to monomorphize
// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
// }
let branch_body = {
let result_val = {
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
let ok_val_symbol = env.new_symbol("val");
let err_val_symbol = env.new_symbol("err");
let ok_branch_expr = {
// Ok {state & first: Ok val},
let mut updates = SendMap::default();
updates.insert(
field_name.clone(),
Field {
var: result_field_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(Expr::Tag {
tag_union_var: result_field_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Ok".into(),
arguments: vec![(
field_var,
Loc::at_zero(Expr::Var(ok_val_symbol, field_var)),
)],
})),
},
);
let updated_record = Expr::RecordUpdate {
record_var: state_record_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
symbol: state_arg_symbol,
updates,
};
Expr::Tag {
tag_union_var: when_expr_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Ok".into(),
arguments: vec![(state_record_var, Loc::at_zero(updated_record))],
}
};
let branches = vec![
// Ok val -> Ok {state & first: Ok val},
WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::AppliedTag {
whole_var: rec_dot_result,
ext_var: Variable::EMPTY_TAG_UNION,
tag_name: "Ok".into(),
arguments: vec![(
field_var,
Loc::at_zero(Pattern::Identifier(ok_val_symbol)),
)],
}),
degenerate: false,
}],
value: Loc::at_zero(ok_branch_expr),
guard: None,
redundant: RedundantMark::known_non_redundant(),
},
// Err err -> Err err
WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::AppliedTag {
whole_var: rec_dot_result,
ext_var: Variable::EMPTY_TAG_UNION,
tag_name: "Err".into(),
arguments: vec![(
decode_err_var,
Loc::at_zero(Pattern::Identifier(err_val_symbol)),
)],
}),
degenerate: false,
}],
value: Loc::at_zero(Expr::Tag {
tag_union_var: when_expr_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Err".into(),
arguments: vec![(
decode_err_var,
Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)),
)],
}),
guard: None,
redundant: RedundantMark::known_non_redundant(),
},
];
// when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
Expr::When {
loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess {
record_var: rec_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: rec_dot_result,
loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
field: "result".into(),
})),
cond_var: rec_dot_result,
expr_var: when_expr_var,
region: Region::zero(),
branches,
branches_cond_var: rec_dot_result,
exhaustive: ExhaustiveMark::known_exhaustive(),
}
};
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
// }
let mut fields_map = SendMap::default();
fields_map.insert(
"rest".into(),
Field {
var: Variable::LIST_U8,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess {
record_var: rec_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: Variable::LIST_U8,
loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
field: "rest".into(),
})),
},
);
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
fields_map.insert(
"result".into(),
Field {
var: when_expr_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(result_val)),
},
);
Expr::Record {
record_var: custom_callback_ret_var,
fields: fields_map,
}
};
let branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)),
degenerate: false,
}],
value: Loc::at_zero(branch_body),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
let condition_expr = Expr::Call(
Box::new((
this_decode_with_var,
Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)),
lambda_set_var,
rec_var,
)),
vec![
(
Variable::LIST_U8,
Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)),
),
(
decoder_var,
Loc::at_zero(Expr::AbilityMember(
Symbol::DECODE_DECODER,
None,
decoder_var,
)),
),
(
fmt_arg_var,
Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)),
),
],
CalledVia::Space,
);
// when Decode.decodeWith bytes Decode.decoder fmt is
Expr::When {
loc_cond: Box::new(Loc::at_zero(condition_expr)),
cond_var: rec_var,
expr_var: custom_callback_ret_var,
region: Region::zero(),
branches: vec![branch],
branches_cond_var: rec_var,
exhaustive: ExhaustiveMark::known_exhaustive(),
}
};
let custom_closure_symbol = env.new_symbol("customCallback");
this_custom_callback_var = env.subs.fresh_unnamed_flex_var();
let custom_callback_lambda_set_var = {
let content = Content::LambdaSet(LambdaSet {
solved: UnionLambdas::insert_into_subs(
env.subs,
[(custom_closure_symbol, [state_record_var])],
),
recursion_var: OptVariable::NONE,
unspecialized: Default::default(),
ambient_function: this_custom_callback_var,
});
let custom_callback_lambda_set_var = synth_var(env.subs, content);
let subs_slice =
SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]);
env.subs.set_content(
this_custom_callback_var,
Content::Structure(FlatType::Func(
subs_slice,
custom_callback_lambda_set_var,
custom_callback_ret_var,
)),
);
custom_callback_lambda_set_var
};
// \bytes, fmt -> …
Expr::Closure(ClosureData {
function_type: this_custom_callback_var,
closure_type: custom_callback_lambda_set_var,
return_type: custom_callback_ret_var,
name: custom_closure_symbol,
captured_symbols: vec![(state_arg_symbol, state_record_var)],
recursive: Recursive::NotRecursive,
arguments: vec![
(
bytes_arg_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)),
),
(
fmt_arg_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)),
),
],
loc_body: Box::new(Loc::at_zero(custom_callback_body)),
})
};
let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
let decode_custom = {
let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var();
let this_decode_custom_var = {
let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]);
let flat_type =
FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var);
synth_var(env.subs, Content::Structure(flat_type))
};
env.unify(decode_custom_var, this_decode_custom_var);
// Decode.custom \bytes, fmt -> …
Expr::Call(
Box::new((
this_decode_custom_var,
Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)),
decode_custom_closure_var,
decode_custom_ret_var,
)),
vec![(this_custom_callback_var, Loc::at_zero(custom_callback))],
CalledVia::Space,
)
};
env.unify(keep_payload_var, decode_custom_ret_var);
let keep = {
// Keep (Decode.custom \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
// }
// )
Expr::Tag {
tag_union_var: keep_or_skip_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Keep".into(),
arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))],
}
};
let branch = {
// "first" ->
// Keep (Decode.custom \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
// }
// )
WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::StrLiteral(field_name.into())),
degenerate: false,
}],
value: Loc::at_zero(keep),
guard: None,
redundant: RedundantMark::known_non_redundant(),
}
};
branches.push(branch);
}
// Example: `_ -> Skip`
let default_branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::Underscore),
degenerate: false,
}],
value: Loc::at_zero(Expr::Tag {
tag_union_var: keep_or_skip_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Skip".into(),
arguments: Vec::new(),
}),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
branches.push(default_branch);
// when field is
let body = Expr::When {
loc_cond: Box::new(Loc::at_zero(Expr::Var(field_arg_symbol, Variable::STR))),
cond_var: Variable::STR,
expr_var: keep_or_skip_var,
region: Region::zero(),
branches,
branches_cond_var: Variable::STR,
exhaustive: ExhaustiveMark::known_exhaustive(),
};
let step_field_closure = env.new_symbol("stepField");
let function_type = env.subs.fresh_unnamed_flex_var();
let closure_type = {
let lambda_set = LambdaSet {
solved: UnionLambdas::tag_without_arguments(env.subs, step_field_closure),
recursion_var: OptVariable::NONE,
unspecialized: Default::default(),
ambient_function: function_type,
};
synth_var(env.subs, Content::LambdaSet(lambda_set))
};
{
let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::STR]);
env.subs.set_content(
function_type,
Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)),
)
};
let expr = Expr::Closure(ClosureData {
function_type,
closure_type,
return_type: keep_or_skip_var,
name: step_field_closure,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
arguments: vec![
(
state_record_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
),
(
Variable::STR,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(field_arg_symbol)),
),
],
loc_body: Box::new(Loc::at_zero(body)),
});
(expr, function_type)
}
// Example:
// finalizer = \rec ->
// when rec.first is
// Ok first ->
// when rec.second is
// Ok second -> Ok {first, second}
// Err NoField -> Err TooShort
// Err NoField -> Err TooShort
fn finalizer(
env: &mut Env,
state_record_var: Variable,
fields: &[Lowercase],
field_vars: &[Variable],
result_field_vars: &[Variable],
) -> (Expr, Variable, Variable) {
let state_arg_symbol = env.new_symbol("stateRecord");
let mut fields_map = SendMap::default();
let mut pattern_symbols = Vec::with_capacity(fields.len());
let decode_err_var = {
let flat_type = FlatType::TagUnion(
UnionTags::tag_without_arguments(env.subs, "TooShort".into()),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(flat_type))
};
for (field_name, &field_var) in fields.iter().zip(field_vars.iter()) {
let symbol = env.new_symbol(field_name.as_str());
pattern_symbols.push(symbol);
let field_expr = Expr::Var(symbol, field_var);
let field = Field {
var: field_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(field_expr)),
};
fields_map.insert(field_name.clone(), field);
}
// The bottom of the happy path - return the decoded record {first: a, second: b} wrapped with
// "Ok".
let return_type_var;
let mut body = {
let subs = &mut env.subs;
let record_field_iter = fields
.iter()
.zip(field_vars.iter())
.map(|(field_name, &field_var)| (field_name.clone(), RecordField::Required(field_var)));
let flat_type = FlatType::Record(
RecordFields::insert_into_subs(subs, record_field_iter),
Variable::EMPTY_RECORD,
);
let done_record_var = synth_var(subs, Content::Structure(flat_type));
let done_record = Expr::Record {
record_var: done_record_var,
fields: fields_map,
};
return_type_var = {
let flat_type = FlatType::TagUnion(
UnionTags::for_result(subs, done_record_var, decode_err_var),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(subs, Content::Structure(flat_type))
};
Expr::Tag {
tag_union_var: return_type_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Ok".into(),
arguments: vec![(done_record_var, Loc::at_zero(done_record))],
}
};
// Unwrap each result in the decoded state
//
// when rec.first is
// Ok first -> ...happy path...
// Err NoField -> Err TooShort
for (((symbol, field_name), &field_var), &result_field_var) in pattern_symbols
.iter()
.rev()
.zip(fields.iter().rev())
.zip(field_vars.iter().rev())
.zip(result_field_vars.iter().rev())
{
// when rec.first is
let cond_expr = Expr::RecordAccess {
record_var: state_record_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: result_field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))),
field: field_name.clone(),
};
// Example: `Ok x -> expr`
let ok_branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::AppliedTag {
whole_var: result_field_var,
ext_var: Variable::EMPTY_TAG_UNION,
tag_name: "Ok".into(),
arguments: vec![(field_var, Loc::at_zero(Pattern::Identifier(*symbol)))],
}),
degenerate: false,
}],
value: Loc::at_zero(body),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
// Example: `_ -> Err TooShort`
let err_branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::Underscore),
degenerate: false,
}],
value: Loc::at_zero(Expr::Tag {
tag_union_var: return_type_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Err".into(),
arguments: vec![(
decode_err_var,
Loc::at_zero(Expr::Tag {
tag_union_var: decode_err_var,
ext_var: Variable::EMPTY_TAG_UNION,
name: "TooShort".into(),
arguments: Vec::new(),
}),
)],
}),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
body = Expr::When {
loc_cond: Box::new(Loc::at_zero(cond_expr)),
cond_var: result_field_var,
expr_var: return_type_var,
region: Region::zero(),
branches: vec![ok_branch, err_branch],
branches_cond_var: result_field_var,
exhaustive: ExhaustiveMark::known_exhaustive(),
};
}
let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later.
let function_symbol = env.new_symbol("finalizer");
let lambda_set = LambdaSet {
solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol),
recursion_var: OptVariable::NONE,
unspecialized: Default::default(),
ambient_function: function_var,
};
let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set));
let flat_type = FlatType::Func(
SubsSlice::insert_into_subs(env.subs, [state_record_var]),
closure_type,
return_type_var,
);
// Fix up function_var so it's not Content::Error anymore
env.subs
.set_content(function_var, Content::Structure(flat_type));
let finalizer = Expr::Closure(ClosureData {
function_type: function_var,
closure_type,
return_type: return_type_var,
name: function_symbol,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
arguments: vec![(
state_record_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
)],
loc_body: Box::new(Loc::at_zero(body)),
});
(finalizer, function_var, decode_err_var)
}
// Example:
// initialState : {first: Result a [NoField], second: Result b [NoField]}
// initialState = {first: Err NoField, second: Err NoField}
fn initial_state(
env: &mut Env<'_>,
field_names: &[Lowercase],
field_vars: &mut Vec<Variable>,
result_field_vars: &mut Vec<Variable>,
) -> (Variable, Expr) {
let mut initial_state_fields = SendMap::default();
for field_name in field_names {
let subs = &mut env.subs;
let field_var = subs.fresh_unnamed_flex_var();
field_vars.push(field_var);
let no_field_label = "NoField";
let union_tags = UnionTags::tag_without_arguments(subs, no_field_label.into());
let no_field_var = synth_var(
subs,
Content::Structure(FlatType::TagUnion(
union_tags,
TagExt::Any(Variable::EMPTY_TAG_UNION),
)),
);
let no_field = Expr::Tag {
tag_union_var: no_field_var,
ext_var: Variable::EMPTY_TAG_UNION,
name: no_field_label.into(),
arguments: Vec::new(),
};
let err_label = "Err";
let union_tags = UnionTags::for_result(subs, field_var, no_field_var);
let result_var = synth_var(
subs,
Content::Structure(FlatType::TagUnion(
union_tags,
TagExt::Any(Variable::EMPTY_TAG_UNION),
)),
);
let field_expr = Expr::Tag {
tag_union_var: result_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: err_label.into(),
arguments: vec![(no_field_var, Loc::at_zero(no_field))],
};
result_field_vars.push(result_var);
let field = Field {
var: result_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(field_expr)),
};
initial_state_fields.insert(field_name.clone(), field);
}
let subs = &mut env.subs;
let record_field_iter = field_names
.iter()
.zip(result_field_vars.iter())
.map(|(field_name, &var)| (field_name.clone(), RecordField::Required(var)));
let flat_type = FlatType::Record(
RecordFields::insert_into_subs(subs, record_field_iter),
Variable::EMPTY_RECORD,
);
let state_record_var = synth_var(subs, Content::Structure(flat_type));
(
state_record_var,
Expr::Record {
record_var: state_record_var,
fields: initial_state_fields,
},
)
}

View file

@ -0,0 +1,994 @@
use roc_can::expr::{
AnnotatedMark, ClosureData, Expr, Field, IntValue, Recursive, WhenBranch, WhenBranchPattern,
};
use roc_can::num::{IntBound, IntLitWidth};
use roc_can::pattern::Pattern;
use roc_collections::SendMap;
use roc_module::called_via::CalledVia;
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{
Content, ExhaustiveMark, FlatType, LambdaSet, OptVariable, RecordFields, RedundantMark,
SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags, Variable,
};
use roc_types::types::RecordField;
use crate::synth_var;
use crate::util::{Env, ExtensionKind};
use super::wrap_in_decode_custom_decode_with;
/// Implements decoding of a tuple. For example, for
///
/// ```text
/// (a, b)
/// ```
///
/// we'd like to generate an impl like
///
/// ```roc
/// decoder : Decoder (a, b) fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting
/// decoder =
/// initialState : {e0: Result a [NoElem], e1: Result b [NoElem]}
/// initialState = {e0: Err NoElem, e1: Err NoElem}
///
/// stepElem = \state, index ->
/// when index is
/// 0 ->
/// Next (Decode.custom \bytes, fmt ->
/// when Decode.decodeWith bytes Decode.decoder fmt is
/// {result, rest} ->
/// {result: Result.map result \val -> {state & e0: Ok val}, rest})
/// 1 ->
/// Next (Decode.custom \bytes, fmt ->
/// when Decode.decodeWith bytes Decode.decoder fmt is
/// {result, rest} ->
/// {result: Result.map result \val -> {state & e1: Ok val}, rest})
/// _ -> TooLong
///
/// finalizer = \st ->
/// when st.e0 is
/// Ok e0 ->
/// when st.e1 is
/// Ok e1 -> Ok (e0, e1)
/// Err NoElem -> Err TooShort
/// Err NoElem -> Err TooShort
///
/// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.tuple initialState stepElem finalizer) fmt
/// ```
pub(crate) fn decoder(env: &mut Env, _def_symbol: Symbol, arity: u32) -> (Expr, Variable) {
// The decoded type of each index in the tuple, e.g. (a, b).
let mut index_vars = Vec::with_capacity(arity as _);
// The type of each index in the decoding state, e.g. {e0: Result a [NoElem], e1: Result b [NoElem]}
let mut state_fields = Vec::with_capacity(arity as _);
let mut state_field_vars = Vec::with_capacity(arity as _);
// initialState = ...
let (state_var, initial_state) = initial_state(
env,
arity,
&mut index_vars,
&mut state_fields,
&mut state_field_vars,
);
// finalizer = ...
let (finalizer, finalizer_var, decode_err_var) = finalizer(
env,
&index_vars,
state_var,
&state_fields,
&state_field_vars,
);
// stepElem = ...
let (step_elem, step_var) = step_elem(
env,
&index_vars,
state_var,
&state_fields,
&state_field_vars,
decode_err_var,
);
// Build up the type of `Decode.tuple` we expect
let tuple_decoder_var = env.subs.fresh_unnamed_flex_var();
let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var();
let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_TUPLE);
let this_decode_record_var = {
let flat_type = FlatType::Func(
SubsSlice::insert_into_subs(env.subs, [state_var, step_var, finalizer_var]),
decode_record_lambda_set,
tuple_decoder_var,
);
synth_var(env.subs, Content::Structure(flat_type))
};
env.unify(decode_record_var, this_decode_record_var);
// Decode.tuple initialState stepElem finalizer
let call_decode_record = Expr::Call(
Box::new((
this_decode_record_var,
Loc::at_zero(Expr::AbilityMember(
Symbol::DECODE_TUPLE,
None,
this_decode_record_var,
)),
decode_record_lambda_set,
tuple_decoder_var,
)),
vec![
(state_var, Loc::at_zero(initial_state)),
(step_var, Loc::at_zero(step_elem)),
(finalizer_var, Loc::at_zero(finalizer)),
],
CalledVia::Space,
);
let (call_decode_custom, decode_custom_ret_var) = {
let bytes_sym = env.new_symbol("bytes");
let fmt_sym = env.new_symbol("fmt");
let fmt_var = env.subs.fresh_unnamed_flex_var();
let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with(
env,
bytes_sym,
(fmt_sym, fmt_var),
vec![],
(call_decode_record, tuple_decoder_var),
);
(decode_custom, decode_custom_var)
};
(call_decode_custom, decode_custom_ret_var)
}
// Example:
// stepElem = \state, index ->
// when index is
// 0 ->
// Next (Decode.custom \bytes, fmt ->
// # Uses a single-branch `when` because `let` is more expensive to monomorphize
// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
// })
//
// "e1" ->
// Next (Decode.custom \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e1: Ok val},
// Err err -> Err err
// })
//
// _ -> TooLong
fn step_elem(
env: &mut Env,
index_vars: &[Variable],
state_record_var: Variable,
state_fields: &[Lowercase],
state_field_vars: &[Variable],
decode_err_var: Variable,
) -> (Expr, Variable) {
let state_arg_symbol = env.new_symbol("stateRecord");
let index_arg_symbol = env.new_symbol("index");
// +1 because of the default branch.
let mut branches = Vec::with_capacity(index_vars.len() + 1);
let keep_payload_var = env.subs.fresh_unnamed_flex_var();
let keep_or_skip_var = {
let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]);
let flat_type = FlatType::TagUnion(
UnionTags::insert_slices_into_subs(
env.subs,
[
("Next".into(), keep_payload_subs_slice),
("TooLong".into(), Default::default()),
],
),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(flat_type))
};
for (((index, state_field), &index_var), &result_index_var) in state_fields
.iter()
.enumerate()
.zip(index_vars)
.zip(state_field_vars)
{
// Example:
// 0 ->
// Next (Decode.custom \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
// }
// )
let this_custom_callback_var;
let custom_callback_ret_var;
let custom_callback = {
// \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
// }
let bytes_arg_symbol = env.new_symbol("bytes");
let fmt_arg_symbol = env.new_symbol("fmt");
let bytes_arg_var = env.subs.fresh_unnamed_flex_var();
let fmt_arg_var = env.subs.fresh_unnamed_flex_var();
// rec.result : [Ok index_var, Err DecodeError]
let rec_dot_result = {
let tag_union = FlatType::TagUnion(
UnionTags::for_result(env.subs, index_var, decode_err_var),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(tag_union))
};
// rec : { rest: List U8, result: (typeof rec.result) }
let rec_var = {
let indexs = RecordFields::insert_into_subs(
env.subs,
[
("rest".into(), RecordField::Required(Variable::LIST_U8)),
("result".into(), RecordField::Required(rec_dot_result)),
],
);
let record = FlatType::Record(indexs, Variable::EMPTY_RECORD);
synth_var(env.subs, Content::Structure(record))
};
// `Decode.decoder` for the index's value
let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
let lambda_set_var = env.subs.fresh_unnamed_flex_var();
let this_decode_with_var = {
let subs_slice = SubsSlice::insert_into_subs(
env.subs,
[bytes_arg_var, decoder_var, fmt_arg_var],
);
let this_decode_with_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)),
);
env.unify(decode_with_var, this_decode_with_var);
this_decode_with_var
};
// The result of decoding this index's value - either the updated state, or a decoding error.
let when_expr_var = {
let flat_type = FlatType::TagUnion(
UnionTags::for_result(env.subs, state_record_var, decode_err_var),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(flat_type))
};
// What our decoder passed to `Decode.custom` returns - the result of decoding the
// index's value, and the remaining bytes.
custom_callback_ret_var = {
let rest_index = RecordField::Required(Variable::LIST_U8);
let result_index = RecordField::Required(when_expr_var);
let flat_type = FlatType::Record(
RecordFields::insert_into_subs(
env.subs,
[("rest".into(), rest_index), ("result".into(), result_index)],
),
Variable::EMPTY_RECORD,
);
synth_var(env.subs, Content::Structure(flat_type))
};
let custom_callback_body = {
let rec_symbol = env.new_symbol("rec");
// # Uses a single-branch `when` because `let` is more expensive to monomorphize
// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
// }
let branch_body = {
let result_val = {
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
let ok_val_symbol = env.new_symbol("val");
let err_val_symbol = env.new_symbol("err");
let ok_branch_expr = {
// Ok {state & e0: Ok val},
let mut updates = SendMap::default();
updates.insert(
state_field.clone(),
Field {
var: result_index_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(Expr::Tag {
tag_union_var: result_index_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Ok".into(),
arguments: vec![(
index_var,
Loc::at_zero(Expr::Var(ok_val_symbol, index_var)),
)],
})),
},
);
let updated_record = Expr::RecordUpdate {
record_var: state_record_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
symbol: state_arg_symbol,
updates,
};
Expr::Tag {
tag_union_var: when_expr_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Ok".into(),
arguments: vec![(state_record_var, Loc::at_zero(updated_record))],
}
};
let branches = vec![
// Ok val -> Ok {state & e0: Ok val},
WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::AppliedTag {
whole_var: rec_dot_result,
ext_var: Variable::EMPTY_TAG_UNION,
tag_name: "Ok".into(),
arguments: vec![(
index_var,
Loc::at_zero(Pattern::Identifier(ok_val_symbol)),
)],
}),
degenerate: false,
}],
value: Loc::at_zero(ok_branch_expr),
guard: None,
redundant: RedundantMark::known_non_redundant(),
},
// Err err -> Err err
WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::AppliedTag {
whole_var: rec_dot_result,
ext_var: Variable::EMPTY_TAG_UNION,
tag_name: "Err".into(),
arguments: vec![(
decode_err_var,
Loc::at_zero(Pattern::Identifier(err_val_symbol)),
)],
}),
degenerate: false,
}],
value: Loc::at_zero(Expr::Tag {
tag_union_var: when_expr_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Err".into(),
arguments: vec![(
decode_err_var,
Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)),
)],
}),
guard: None,
redundant: RedundantMark::known_non_redundant(),
},
];
// when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
Expr::When {
loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess {
record_var: rec_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: rec_dot_result,
loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
field: "result".into(),
})),
cond_var: rec_dot_result,
expr_var: when_expr_var,
region: Region::zero(),
branches,
branches_cond_var: rec_dot_result,
exhaustive: ExhaustiveMark::known_exhaustive(),
}
};
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
// }
let mut fields_map = SendMap::default();
fields_map.insert(
"rest".into(),
Field {
var: Variable::LIST_U8,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess {
record_var: rec_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: Variable::LIST_U8,
loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
field: "rest".into(),
})),
},
);
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
fields_map.insert(
"result".into(),
Field {
var: when_expr_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(result_val)),
},
);
Expr::Record {
record_var: custom_callback_ret_var,
fields: fields_map,
}
};
let branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)),
degenerate: false,
}],
value: Loc::at_zero(branch_body),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
let condition_expr = Expr::Call(
Box::new((
this_decode_with_var,
Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)),
lambda_set_var,
rec_var,
)),
vec![
(
Variable::LIST_U8,
Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)),
),
(
decoder_var,
Loc::at_zero(Expr::AbilityMember(
Symbol::DECODE_DECODER,
None,
decoder_var,
)),
),
(
fmt_arg_var,
Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)),
),
],
CalledVia::Space,
);
// when Decode.decodeWith bytes Decode.decoder fmt is
Expr::When {
loc_cond: Box::new(Loc::at_zero(condition_expr)),
cond_var: rec_var,
expr_var: custom_callback_ret_var,
region: Region::zero(),
branches: vec![branch],
branches_cond_var: rec_var,
exhaustive: ExhaustiveMark::known_exhaustive(),
}
};
let custom_closure_symbol = env.new_symbol("customCallback");
this_custom_callback_var = env.subs.fresh_unnamed_flex_var();
let custom_callback_lambda_set_var = {
let content = Content::LambdaSet(LambdaSet {
solved: UnionLambdas::insert_into_subs(
env.subs,
[(custom_closure_symbol, [state_record_var])],
),
recursion_var: OptVariable::NONE,
unspecialized: Default::default(),
ambient_function: this_custom_callback_var,
});
let custom_callback_lambda_set_var = synth_var(env.subs, content);
let subs_slice =
SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]);
env.subs.set_content(
this_custom_callback_var,
Content::Structure(FlatType::Func(
subs_slice,
custom_callback_lambda_set_var,
custom_callback_ret_var,
)),
);
custom_callback_lambda_set_var
};
// \bytes, fmt -> …
Expr::Closure(ClosureData {
function_type: this_custom_callback_var,
closure_type: custom_callback_lambda_set_var,
return_type: custom_callback_ret_var,
name: custom_closure_symbol,
captured_symbols: vec![(state_arg_symbol, state_record_var)],
recursive: Recursive::NotRecursive,
arguments: vec![
(
bytes_arg_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)),
),
(
fmt_arg_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)),
),
],
loc_body: Box::new(Loc::at_zero(custom_callback_body)),
})
};
let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
let decode_custom = {
let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var();
let this_decode_custom_var = {
let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]);
let flat_type =
FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var);
synth_var(env.subs, Content::Structure(flat_type))
};
env.unify(decode_custom_var, this_decode_custom_var);
// Decode.custom \bytes, fmt -> …
Expr::Call(
Box::new((
this_decode_custom_var,
Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)),
decode_custom_closure_var,
decode_custom_ret_var,
)),
vec![(this_custom_callback_var, Loc::at_zero(custom_callback))],
CalledVia::Space,
)
};
env.unify(keep_payload_var, decode_custom_ret_var);
let keep = {
// Next (Decode.custom \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
// }
// )
Expr::Tag {
tag_union_var: keep_or_skip_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Next".into(),
arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))],
}
};
let branch = {
// 0 ->
// Next (Decode.custom \bytes, fmt ->
// when Decode.decodeWith bytes Decode.decoder fmt is
// rec ->
// {
// rest: rec.rest,
// result: when rec.result is
// Ok val -> Ok {state & e0: Ok val},
// Err err -> Err err
// }
// )
WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::IntLiteral(
Variable::NAT,
Variable::NATURAL,
index.to_string().into_boxed_str(),
IntValue::I128((index as i128).to_ne_bytes()),
IntBound::Exact(IntLitWidth::Nat),
)),
degenerate: false,
}],
value: Loc::at_zero(keep),
guard: None,
redundant: RedundantMark::known_non_redundant(),
}
};
branches.push(branch);
}
// Example: `_ -> TooLong`
let default_branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::Underscore),
degenerate: false,
}],
value: Loc::at_zero(Expr::Tag {
tag_union_var: keep_or_skip_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "TooLong".into(),
arguments: Vec::new(),
}),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
branches.push(default_branch);
// when index is
let body = Expr::When {
loc_cond: Box::new(Loc::at_zero(Expr::Var(index_arg_symbol, Variable::NAT))),
cond_var: Variable::NAT,
expr_var: keep_or_skip_var,
region: Region::zero(),
branches,
branches_cond_var: Variable::NAT,
exhaustive: ExhaustiveMark::known_exhaustive(),
};
let step_elem_closure = env.new_symbol("stepElem");
let function_type = env.subs.fresh_unnamed_flex_var();
let closure_type = {
let lambda_set = LambdaSet {
solved: UnionLambdas::tag_without_arguments(env.subs, step_elem_closure),
recursion_var: OptVariable::NONE,
unspecialized: Default::default(),
ambient_function: function_type,
};
synth_var(env.subs, Content::LambdaSet(lambda_set))
};
{
let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::NAT]);
env.subs.set_content(
function_type,
Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)),
)
};
let expr = Expr::Closure(ClosureData {
function_type,
closure_type,
return_type: keep_or_skip_var,
name: step_elem_closure,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
arguments: vec![
(
state_record_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
),
(
Variable::NAT,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(index_arg_symbol)),
),
],
loc_body: Box::new(Loc::at_zero(body)),
});
(expr, function_type)
}
// Example:
// finalizer = \rec ->
// when rec.e0 is
// Ok e0 ->
// when rec.e1 is
// Ok e1 -> Ok (e0, e1)
// Err NoElem -> Err TooShort
// Err NoElem -> Err TooShort
fn finalizer(
env: &mut Env,
index_vars: &[Variable],
state_record_var: Variable,
state_fields: &[Lowercase],
state_field_vars: &[Variable],
) -> (Expr, Variable, Variable) {
let state_arg_symbol = env.new_symbol("stateRecord");
let mut tuple_elems = Vec::with_capacity(index_vars.len());
let mut pattern_symbols = Vec::with_capacity(index_vars.len());
let decode_err_var = {
let flat_type = FlatType::TagUnion(
UnionTags::tag_without_arguments(env.subs, "TooShort".into()),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(env.subs, Content::Structure(flat_type))
};
for (i, &index_var) in index_vars.iter().enumerate() {
let symbol = env.new_symbol(i);
pattern_symbols.push(symbol);
let index_expr = Expr::Var(symbol, index_var);
tuple_elems.push((index_var, Box::new(Loc::at_zero(index_expr))));
}
// The bottom of the happy path - return the decoded tuple (a, b) wrapped with
// "Ok".
let return_type_var;
let mut body = {
let subs = &mut env.subs;
let tuple_indices_iter = index_vars.iter().copied().enumerate();
let flat_type = FlatType::Tuple(
TupleElems::insert_into_subs(subs, tuple_indices_iter),
Variable::EMPTY_TUPLE,
);
let done_tuple_var = synth_var(subs, Content::Structure(flat_type));
let done_record = Expr::Tuple {
tuple_var: done_tuple_var,
elems: tuple_elems,
};
return_type_var = {
let flat_type = FlatType::TagUnion(
UnionTags::for_result(subs, done_tuple_var, decode_err_var),
TagExt::Any(Variable::EMPTY_TAG_UNION),
);
synth_var(subs, Content::Structure(flat_type))
};
Expr::Tag {
tag_union_var: return_type_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Ok".into(),
arguments: vec![(done_tuple_var, Loc::at_zero(done_record))],
}
};
// Unwrap each result in the decoded state
//
// when rec.e0 is
// Ok e0 -> ...happy path...
// Err NoElem -> Err TooShort
for (((symbol, field), &index_var), &result_index_var) in pattern_symbols
.iter()
.zip(state_fields)
.zip(index_vars)
.zip(state_field_vars)
.rev()
{
// when rec.e0 is
let cond_expr = Expr::RecordAccess {
record_var: state_record_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: result_index_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))),
field: field.clone(),
};
// Example: `Ok x -> expr`
let ok_branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::AppliedTag {
whole_var: result_index_var,
ext_var: Variable::EMPTY_TAG_UNION,
tag_name: "Ok".into(),
arguments: vec![(index_var, Loc::at_zero(Pattern::Identifier(*symbol)))],
}),
degenerate: false,
}],
value: Loc::at_zero(body),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
// Example: `_ -> Err TooShort`
let err_branch = WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::Underscore),
degenerate: false,
}],
value: Loc::at_zero(Expr::Tag {
tag_union_var: return_type_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: "Err".into(),
arguments: vec![(
decode_err_var,
Loc::at_zero(Expr::Tag {
tag_union_var: decode_err_var,
ext_var: Variable::EMPTY_TAG_UNION,
name: "TooShort".into(),
arguments: Vec::new(),
}),
)],
}),
guard: None,
redundant: RedundantMark::known_non_redundant(),
};
body = Expr::When {
loc_cond: Box::new(Loc::at_zero(cond_expr)),
cond_var: result_index_var,
expr_var: return_type_var,
region: Region::zero(),
branches: vec![ok_branch, err_branch],
branches_cond_var: result_index_var,
exhaustive: ExhaustiveMark::known_exhaustive(),
};
}
let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later.
let function_symbol = env.new_symbol("finalizer");
let lambda_set = LambdaSet {
solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol),
recursion_var: OptVariable::NONE,
unspecialized: Default::default(),
ambient_function: function_var,
};
let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set));
let flat_type = FlatType::Func(
SubsSlice::insert_into_subs(env.subs, [state_record_var]),
closure_type,
return_type_var,
);
// Fix up function_var so it's not Content::Error anymore
env.subs
.set_content(function_var, Content::Structure(flat_type));
let finalizer = Expr::Closure(ClosureData {
function_type: function_var,
closure_type,
return_type: return_type_var,
name: function_symbol,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
arguments: vec![(
state_record_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
)],
loc_body: Box::new(Loc::at_zero(body)),
});
(finalizer, function_var, decode_err_var)
}
// Example:
// initialState : {e0: Result a [NoElem], e1: Result b [NoElem]}
// initialState = {e0: Err NoElem, e1: Err NoElem}
fn initial_state(
env: &mut Env<'_>,
arity: u32,
index_vars: &mut Vec<Variable>,
state_fields: &mut Vec<Lowercase>,
state_field_vars: &mut Vec<Variable>,
) -> (Variable, Expr) {
let mut initial_state_fields = SendMap::default();
for i in 0..arity {
let subs = &mut env.subs;
let index_var = subs.fresh_unnamed_flex_var();
index_vars.push(index_var);
let state_field = Lowercase::from(format!("e{i}"));
state_fields.push(state_field.clone());
let no_index_label = "NoElem";
let union_tags = UnionTags::tag_without_arguments(subs, no_index_label.into());
let no_index_var = synth_var(
subs,
Content::Structure(FlatType::TagUnion(
union_tags,
TagExt::Any(Variable::EMPTY_TAG_UNION),
)),
);
let no_index = Expr::Tag {
tag_union_var: no_index_var,
ext_var: Variable::EMPTY_TAG_UNION,
name: no_index_label.into(),
arguments: Vec::new(),
};
let err_label = "Err";
let union_tags = UnionTags::for_result(subs, index_var, no_index_var);
let result_var = synth_var(
subs,
Content::Structure(FlatType::TagUnion(
union_tags,
TagExt::Any(Variable::EMPTY_TAG_UNION),
)),
);
let index_expr = Expr::Tag {
tag_union_var: result_var,
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
name: err_label.into(),
arguments: vec![(no_index_var, Loc::at_zero(no_index))],
};
state_field_vars.push(result_var);
let index = Field {
var: result_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(index_expr)),
};
initial_state_fields.insert(state_field, index);
}
let subs = &mut env.subs;
let record_index_iter = state_fields
.iter()
.zip(state_field_vars.iter())
.map(|(index_name, &var)| (index_name.clone(), RecordField::Required(var)));
let flat_type = FlatType::Record(
RecordFields::insert_into_subs(subs, record_index_iter),
Variable::EMPTY_RECORD,
);
let state_record_var = synth_var(subs, Content::Structure(flat_type));
(
state_record_var,
Expr::Record {
record_var: state_record_var,
fields: initial_state_fields,
},
)
}

View file

@ -14,7 +14,8 @@ use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
RedundantMark, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
RedundantMark, SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags, Variable,
VariableSubsSlice,
};
use roc_types::types::RecordField;
@ -50,6 +51,21 @@ pub(crate) fn derive_to_encoder(
to_encoder_record(env, record_var, fields, def_symbol)
}
FlatEncodableKey::Tuple(arity) => {
// Generalized tuple var so we can reuse this impl between many tuples:
// if arity = n, this is (t1, ..., tn) for fresh t1, ..., tn.
let flex_elems = (0..arity)
.into_iter()
.map(|idx| (idx as usize, env.subs.fresh_unnamed_flex_var()))
.collect::<Vec<_>>();
let elems = TupleElems::insert_into_subs(env.subs, flex_elems);
let tuple_var = synth_var(
env.subs,
Content::Structure(FlatType::Tuple(elems, Variable::EMPTY_TUPLE)),
);
to_encoder_tuple(env, tuple_var, elems, def_symbol)
}
FlatEncodableKey::TagUnion(tags) => {
// Generalized tag union var so we can reuse this impl between many unions:
// if tags = [ A arity=2, B arity=1 ], this is [ A t1 t2, B t3 ] for fresh t1, t2, t3
@ -490,6 +506,189 @@ fn to_encoder_record(
(clos, fn_var)
}
fn to_encoder_tuple(
env: &mut Env<'_>,
tuple_var: Variable,
elems: TupleElems,
fn_name: Symbol,
) -> (Expr, Variable) {
// Suppose tup = (t1, t2). Build
//
// \tup -> Encode.tuple [
// Encode.toEncoder tup.0,
// Encode.toEncoder tup.1,
// ]
let tup_sym = env.new_symbol("tup");
let whole_encoder_in_list_var = env.subs.fresh_unnamed_flex_var(); // type of the encoder in the list
use Expr::*;
let elem_encoders_list = elems
.iter_all()
.map(|(elem_index, elem_var_index)| {
let index = env.subs[elem_index];
let elem_var = env.subs[elem_var_index];
let elem_var_slice = VariableSubsSlice::new(elem_var_index.index, 1);
// tup.0
let tuple_access = TupleAccess {
tuple_var,
ext_var: env.subs.fresh_unnamed_flex_var(),
elem_var,
loc_expr: Box::new(Loc::at_zero(Var(
tup_sym,
env.subs.fresh_unnamed_flex_var(),
))),
index,
};
// build `toEncoder tup.0` type
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
let to_encoder_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
// (typeof tup.0) -[clos]-> t1
let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1
let this_to_encoder_fn_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(
elem_var_slice,
to_encoder_clos_var,
encoder_var,
)),
);
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
// ~ (typeof tup.0) -[clos]-> t1
env.unify(to_encoder_fn_var, this_to_encoder_fn_var);
// toEncoder : (typeof tup.0) -[clos]-> Encoder fmt | fmt has EncoderFormatting
let to_encoder_var = AbilityMember(Symbol::ENCODE_TO_ENCODER, None, to_encoder_fn_var);
let to_encoder_fn = Box::new((
to_encoder_fn_var,
Loc::at_zero(to_encoder_var),
to_encoder_clos_var,
encoder_var,
));
// toEncoder tup.0
let to_encoder_call = Call(
to_encoder_fn,
vec![(elem_var, Loc::at_zero(tuple_access))],
CalledVia::Space,
);
// NOTE: must be done to unify the lambda sets under `encoder_var`
env.unify(encoder_var, whole_encoder_in_list_var);
Loc::at_zero(to_encoder_call)
})
.collect::<Vec<_>>();
// typeof [ toEncoder tup.0, toEncoder tup.1 ]
let whole_encoder_in_list_var_slice =
VariableSubsSlice::insert_into_subs(env.subs, once(whole_encoder_in_list_var));
let elem_encoders_list_var = synth_var(
env.subs,
Content::Structure(FlatType::Apply(
Symbol::LIST_LIST,
whole_encoder_in_list_var_slice,
)),
);
// [ toEncoder tup.0, toEncoder tup.1 ]
let elem_encoders_list = List {
elem_var: whole_encoder_in_list_var,
loc_elems: elem_encoders_list,
};
// build `Encode.tuple [ toEncoder tup.0, toEncoder tup.1 ]` type
// List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
let encode_tuple_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TUPLE);
// elem_encoders_list_var -[clos]-> t1
let elem_encoders_list_var_slice =
VariableSubsSlice::insert_into_subs(env.subs, once(elem_encoders_list_var));
let encode_tuple_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1
let this_encode_tuple_fn_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(
elem_encoders_list_var_slice,
encode_tuple_clos_var,
encoder_var,
)),
);
// List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
// ~ elem_encoders_list_var -[clos]-> t1
env.unify(encode_tuple_fn_var, this_encode_tuple_fn_var);
// Encode.tuple : elem_encoders_list_var -[clos]-> Encoder fmt | fmt has EncoderFormatting
let encode_tuple_var = AbilityMember(Symbol::ENCODE_TUPLE, None, encode_tuple_fn_var);
let encode_tuple_fn = Box::new((
encode_tuple_fn_var,
Loc::at_zero(encode_tuple_var),
encode_tuple_clos_var,
encoder_var,
));
// Encode.tuple [ { key: .., value: .. }, .. ]
let encode_tuple_call = Call(
encode_tuple_fn,
vec![(elem_encoders_list_var, Loc::at_zero(elem_encoders_list))],
CalledVia::Space,
);
// Encode.custom \bytes, fmt -> Encode.appendWith bytes (Encode.tuple_var ..) fmt
let (body, this_encoder_var) =
wrap_in_encode_custom(env, encode_tuple_call, encoder_var, tup_sym, tuple_var);
// Create fn_var for ambient capture; we fix it up below.
let fn_var = synth_var(env.subs, Content::Error);
// -[fn_name]->
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![])));
let fn_clos_var = synth_var(
env.subs,
Content::LambdaSet(LambdaSet {
solved: fn_name_labels,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: fn_var,
}),
);
// typeof tup -[fn_name]-> (typeof Encode.tuple [ .. ] = Encoder fmt)
let tuple_var_slice = SubsSlice::insert_into_subs(env.subs, once(tuple_var));
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(
tuple_var_slice,
fn_clos_var,
this_encoder_var,
)),
);
// \tup -[fn_name]-> Encode.tuple [ { key: .., value: .. }, .. ]
let clos = Closure(ClosureData {
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
name: fn_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
arguments: vec![(
tuple_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(tup_sym)),
)],
loc_body: Box::new(Loc::at_zero(body)),
});
(clos, fn_var)
}
fn to_encoder_tag_union(
env: &mut Env<'_>,
tag_union_var: Variable,

View file

@ -19,8 +19,8 @@ use roc_types::{
num::int_lit_width_to_variable,
subs::{
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
RedundantMark, Subs, SubsIndex, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable,
VariableSubsSlice,
RedundantMark, Subs, SubsIndex, SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags,
Variable, VariableSubsSlice,
},
types::RecordField,
};
@ -30,6 +30,7 @@ use crate::{synth_var, util::Env, DerivedBody};
pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody {
let (body_type, body) = match key {
FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields),
FlatHashKey::Tuple(arity) => hash_tuple(env, def_symbol, arity),
FlatHashKey::TagUnion(tags) => {
if tags.len() == 1 {
hash_newtype_tag_union(env, def_symbol, tags.into_iter().next().unwrap())
@ -122,6 +123,76 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (V
)
}
fn hash_tuple(env: &mut Env<'_>, fn_name: Symbol, arity: u32) -> (Variable, Expr) {
// Suppose tup = (v1, ..., vn).
// Build a generalized type t_tup = (t1, ..., tn), with fresh t1, ..., tn,
// so that we can re-use the derived impl for many tuples of the same arity.
let (tuple_var, tuple_elems) = {
// TODO: avoid an allocation here by pre-allocating the indices and variables `TupleElems`
// will be instantiated with.
let flex_elems: Vec<_> = (0..arity)
.into_iter()
.map(|i| (i as usize, env.subs.fresh_unnamed_flex_var()))
.collect();
let elems = TupleElems::insert_into_subs(env.subs, flex_elems);
let tuple_var = synth_var(
env.subs,
Content::Structure(FlatType::Tuple(elems, Variable::EMPTY_TUPLE)),
);
(tuple_var, elems)
};
// Now, a hasher for this tuple is
//
// hash_tup : hasher, (t1, ..., tn) -> hasher | hasher has Hasher
// hash_tup = \hasher, tup ->
// Hash.hash (
// Hash.hash
// ...
// (Hash.hash hasher tup.0)
// ...
// tup.n1)
// tup.n
//
// So, just a build a fold travelling up the elements.
let tup_sym = env.new_symbol("tup");
let hasher_sym = env.new_symbol("hasher");
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Subs::AB_HASHER));
let (body_var, body) = tuple_elems.iter_all().fold(
(hasher_var, Expr::Var(hasher_sym, hasher_var)),
|total_hasher, (elem_idx, elem_var)| {
let index = env.subs[elem_idx];
let elem_var = env.subs[elem_var];
let elem_access = Expr::TupleAccess {
tuple_var,
elem_var,
ext_var: env.subs.fresh_unnamed_flex_var(),
loc_expr: Box::new(Loc::at_zero(Expr::Var(
tup_sym,
env.subs.fresh_unnamed_flex_var(),
))),
index,
};
call_hash_hash(env, total_hasher, (elem_var, elem_access))
},
);
// Finally, build the closure
// \hasher, rcd -> body
build_outer_derived_closure(
env,
fn_name,
(hasher_var, hasher_sym),
(tuple_var, Pattern::Identifier(tup_sym)),
(body_var, body),
)
}
/// Build a `hash` implementation for a non-singleton tag union.
fn hash_tag_union(
env: &mut Env<'_>,

View file

@ -18,19 +18,20 @@ pub(crate) struct Env<'a> {
}
impl Env<'_> {
pub fn new_symbol(&mut self, name_hint: &str) -> Symbol {
pub fn new_symbol(&mut self, name_hint: impl std::string::ToString) -> Symbol {
if cfg!(any(
debug_assertions,
test,
feature = "debug-derived-symbols"
)) {
let mut i = 0;
let hint = name_hint.to_string();
let debug_name = loop {
i += 1;
let name = if i == 1 {
name_hint.to_owned()
hint.clone()
} else {
format!("{}{}", name_hint, i)
format!("{}{}", hint, i)
};
if self.derived_ident_ids.get_id(&name).is_none() {
break name;

View file

@ -1,14 +1,14 @@
[package]
name = "roc_derive_key"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }

View file

@ -2,7 +2,7 @@ use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable};
use crate::{
util::{check_derivable_ext_var, debug_name_record},
util::{check_derivable_ext_var, debug_name_record, debug_name_tuple},
DeriveError,
};
@ -18,6 +18,7 @@ pub enum FlatDecodableKey {
// Unfortunate that we must allocate here, c'est la vie
Record(Vec<Lowercase>),
Tuple(u32),
}
impl FlatDecodableKey {
@ -25,6 +26,7 @@ impl FlatDecodableKey {
match self {
FlatDecodableKey::List() => "list".to_string(),
FlatDecodableKey::Record(fields) => debug_name_record(fields),
FlatDecodableKey::Tuple(arity) => debug_name_tuple(*arity),
}
}
}
@ -61,8 +63,14 @@ impl FlatDecodable {
Ok(Key(FlatDecodableKey::Record(field_names)))
}
FlatType::Tuple(_elems, _ext) => {
todo!()
FlatType::Tuple(elems, ext) => {
let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
check_derivable_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTuple))
})?;
Ok(Key(FlatDecodableKey::Tuple(elems_iter.count() as _)))
}
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
Err(Underivable) // yet
@ -78,27 +86,18 @@ impl FlatDecodable {
//
FlatType::Func(..) => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match sym {
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::DECODE_U8)),
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::DECODE_U16)),
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::DECODE_U32)),
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::DECODE_U64)),
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::DECODE_U128)),
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::DECODE_I8)),
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::DECODE_I16)),
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::DECODE_I32)),
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::DECODE_I64)),
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::DECODE_I128)),
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::DECODE_DEC)),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::DECODE_F32)),
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::DECODE_F64)),
Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) {
Some(lambda) => lambda,
// NB: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.
_ => Self::from_var(subs, real_var),
None => Self::from_var(subs, real_var),
},
Content::RangedNumber(_) => Err(Underivable),
Content::RangedNumber(range) => {
Self::from_var(subs, range.default_compilation_variable())
}
//
Content::RecursionVar { structure, .. } => Self::from_var(subs, structure),
//
Content::RecursionVar { .. } => Err(Underivable),
Content::Error => Err(Underivable),
Content::FlexVar(_)
| Content::RigidVar(_)
@ -107,4 +106,30 @@ impl FlatDecodable {
Content::LambdaSet(_) => Err(Underivable),
}
}
pub(crate) fn from_builtin_symbol(symbol: Symbol) -> Result<FlatDecodable, DeriveError> {
from_builtin_symbol(symbol).unwrap_or(Err(DeriveError::Underivable))
}
}
const fn from_builtin_symbol(symbol: Symbol) -> Option<Result<FlatDecodable, DeriveError>> {
use FlatDecodable::*;
match symbol {
Symbol::BOOL_BOOL => Some(Ok(Immediate(Symbol::DECODE_BOOL))),
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(Ok(Immediate(Symbol::DECODE_U8))),
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(Ok(Immediate(Symbol::DECODE_U16))),
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(Ok(Immediate(Symbol::DECODE_U32))),
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(Ok(Immediate(Symbol::DECODE_U64))),
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(Ok(Immediate(Symbol::DECODE_U128))),
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(Ok(Immediate(Symbol::DECODE_I8))),
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(Ok(Immediate(Symbol::DECODE_I16))),
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(Ok(Immediate(Symbol::DECODE_I32))),
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(Ok(Immediate(Symbol::DECODE_I64))),
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(Ok(Immediate(Symbol::DECODE_I128))),
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::DECODE_DEC))),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::DECODE_F32))),
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::DECODE_F64))),
Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)),
_ => None,
}
}

View file

@ -5,7 +5,7 @@ use roc_module::{
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
use crate::{
util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
util::{check_derivable_ext_var, debug_name_record, debug_name_tag, debug_name_tuple},
DeriveError,
};
@ -22,6 +22,7 @@ pub enum FlatEncodableKey {
Dict(/* takes two variables */),
// Unfortunate that we must allocate here, c'est la vie
Record(Vec<Lowercase>),
Tuple(u32),
TagUnion(Vec<(TagName, u16)>),
}
@ -32,6 +33,7 @@ impl FlatEncodableKey {
FlatEncodableKey::Set() => "set".to_string(),
FlatEncodableKey::Dict() => "dict".to_string(),
FlatEncodableKey::Record(fields) => debug_name_record(fields),
FlatEncodableKey::Tuple(arity) => debug_name_tuple(*arity),
FlatEncodableKey::TagUnion(tags) => debug_name_tag(tags),
}
}
@ -66,8 +68,14 @@ impl FlatEncodable {
Ok(Key(FlatEncodableKey::Record(field_names)))
}
FlatType::Tuple(_elems, _ext) => {
todo!()
FlatType::Tuple(elems, ext) => {
let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
check_derivable_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTuple))
})?;
Ok(Key(FlatEncodableKey::Tuple(elems_iter.count() as _)))
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
// The recursion var doesn't matter, because the derived implementation will only
@ -112,27 +120,18 @@ impl FlatEncodable {
//
FlatType::Func(..) => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match sym {
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::ENCODE_U8)),
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::ENCODE_U16)),
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::ENCODE_U32)),
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::ENCODE_U64)),
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::ENCODE_U128)),
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::ENCODE_I8)),
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::ENCODE_I16)),
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::ENCODE_I32)),
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::ENCODE_I64)),
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::ENCODE_I128)),
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::ENCODE_DEC)),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::ENCODE_F32)),
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::ENCODE_F64)),
Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) {
Some(lambda) => lambda,
// TODO: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.
_ => Self::from_var(subs, real_var),
},
Content::RangedNumber(_) => Err(Underivable),
Content::RangedNumber(range) => {
Self::from_var(subs, range.default_compilation_variable())
}
//
Content::RecursionVar { structure, .. } => Self::from_var(subs, structure),
//
Content::RecursionVar { .. } => Err(Underivable),
Content::Error => Err(Underivable),
Content::FlexVar(_)
| Content::RigidVar(_)
@ -141,4 +140,30 @@ impl FlatEncodable {
Content::LambdaSet(_) => Err(Underivable),
}
}
pub(crate) fn from_builtin_symbol(symbol: Symbol) -> Result<FlatEncodable, DeriveError> {
from_builtin_symbol(symbol).unwrap_or(Err(DeriveError::Underivable))
}
}
const fn from_builtin_symbol(symbol: Symbol) -> Option<Result<FlatEncodable, DeriveError>> {
use FlatEncodable::*;
match symbol {
Symbol::BOOL_BOOL => Some(Ok(Immediate(Symbol::ENCODE_BOOL))),
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(Ok(Immediate(Symbol::ENCODE_U8))),
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(Ok(Immediate(Symbol::ENCODE_U16))),
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(Ok(Immediate(Symbol::ENCODE_U32))),
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(Ok(Immediate(Symbol::ENCODE_U64))),
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(Ok(Immediate(Symbol::ENCODE_U128))),
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(Ok(Immediate(Symbol::ENCODE_I8))),
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(Ok(Immediate(Symbol::ENCODE_I16))),
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(Ok(Immediate(Symbol::ENCODE_I32))),
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(Ok(Immediate(Symbol::ENCODE_I64))),
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(Ok(Immediate(Symbol::ENCODE_I128))),
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::ENCODE_DEC))),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::ENCODE_F32))),
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::ENCODE_F64))),
Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)),
_ => None,
}
}

View file

@ -5,7 +5,7 @@ use roc_module::{
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
use crate::{
util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
util::{check_derivable_ext_var, debug_name_record, debug_name_tag, debug_name_tuple},
DeriveError,
};
@ -21,6 +21,7 @@ pub enum FlatHash {
pub enum FlatHashKey {
// Unfortunate that we must allocate here, c'est la vie
Record(Vec<Lowercase>),
Tuple(u32),
TagUnion(Vec<(TagName, u16)>),
}
@ -28,6 +29,7 @@ impl FlatHashKey {
pub(crate) fn debug_name(&self) -> String {
match self {
FlatHashKey::Record(fields) => debug_name_record(fields),
FlatHashKey::Tuple(arity) => debug_name_tuple(*arity),
FlatHashKey::TagUnion(tags) => debug_name_tag(tags),
}
}
@ -65,8 +67,14 @@ impl FlatHash {
Ok(Key(FlatHashKey::Record(field_names)))
}
FlatType::Tuple(_elems, _ext) => {
todo!();
FlatType::Tuple(elems, ext) => {
let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
check_derivable_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTuple))
})?;
Ok(Key(FlatHashKey::Tuple(elems_iter.count() as _)))
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
// The recursion var doesn't matter, because the derived implementation will only
@ -109,7 +117,7 @@ impl FlatHash {
//
FlatType::Func(..) => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match num_symbol_to_hash_lambda(sym) {
Content::Alias(sym, _, real_var, _) => match builtin_symbol_to_hash_lambda(sym) {
Some(lambda) => Ok(lambda),
// NB: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.
@ -129,7 +137,7 @@ impl FlatHash {
// during monomorphization, at which point we always choose a default layout
// for ranged numbers, without concern for reification to a ground type.
let chosen_width = range.default_compilation_width();
let lambda = num_symbol_to_hash_lambda(chosen_width.symbol()).unwrap();
let lambda = builtin_symbol_to_hash_lambda(chosen_width.symbol()).unwrap();
Ok(lambda)
}
//
@ -143,11 +151,16 @@ impl FlatHash {
Content::LambdaSet(_) => Err(Underivable),
}
}
pub fn from_builtin_symbol(symbol: Symbol) -> Result<FlatHash, DeriveError> {
builtin_symbol_to_hash_lambda(symbol).ok_or(DeriveError::Underivable)
}
}
const fn num_symbol_to_hash_lambda(symbol: Symbol) -> Option<FlatHash> {
const fn builtin_symbol_to_hash_lambda(symbol: Symbol) -> Option<FlatHash> {
use FlatHash::*;
match symbol {
Symbol::BOOL_BOOL => Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_BOOL)),
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U8))
}

View file

@ -52,7 +52,7 @@ impl DeriveKey {
}
}
#[derive(Hash, PartialEq, Eq, Debug)]
#[derive(Hash, Clone, PartialEq, Eq, Debug)]
pub enum Derived {
/// If a derived implementation name is well-known ahead-of-time, we can inline the symbol
/// directly rather than associating a key for an implementation to be made later on.
@ -123,4 +123,34 @@ impl Derived {
}
}
}
pub fn builtin_with_builtin_symbol(
builtin: DeriveBuiltin,
symbol: Symbol,
) -> Result<Self, DeriveError> {
match builtin {
DeriveBuiltin::ToEncoder => match encoding::FlatEncodable::from_builtin_symbol(symbol)?
{
FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))),
},
DeriveBuiltin::Decoder => match decoding::FlatDecodable::from_builtin_symbol(symbol)? {
FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))),
},
DeriveBuiltin::Hash => match hash::FlatHash::from_builtin_symbol(symbol)? {
FlatHash::SingleLambdaSetImmediate(imm) => {
Ok(Derived::SingleLambdaSetImmediate(imm))
}
FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))),
},
DeriveBuiltin::IsEq => {
// If obligation checking passes, we always lower derived implementations of `isEq`
// to the `Eq` low-level, to be fulfilled by the backends.
Ok(Derived::SingleLambdaSetImmediate(
Symbol::BOOL_STRUCTURAL_EQ,
))
}
}
}
}

View file

@ -43,6 +43,10 @@ pub(crate) fn debug_name_record(fields: &[Lowercase]) -> String {
str
}
pub(crate) fn debug_name_tuple(arity: u32) -> String {
format!("(arity:{arity})")
}
pub(crate) fn debug_name_tag(tags: &[(TagName, u16)]) -> String {
let mut str = String::from('[');
tags.iter().enumerate().for_each(|(i, (tag, arity))| {

View file

@ -1,14 +1,15 @@
[package]
name = "roc_exhaustive"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides exhaustiveness checking for Roc."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }

View file

@ -38,6 +38,7 @@ pub enum RenderAs {
Tag,
Opaque,
Record(Vec<Lowercase>),
Tuple,
Guard,
}

View file

@ -1,14 +1,16 @@
[package]
name = "roc_fmt"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "The roc code formatter."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
bumpalo.workspace = true
roc_region = { path = "../region" }
bumpalo.workspace = true

View file

@ -177,7 +177,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline())
}
Tuple { fields, ext } => {
Tuple { elems: fields, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
@ -343,7 +343,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
}
}
Tuple { fields, ext } => {
Tuple { elems: fields, ext } => {
fmt_collection(buf, indent, Braces::Round, *fields, newlines);
if let Some(loc_ext_ann) = *ext {
@ -509,6 +509,7 @@ fn format_assigned_field_help<'a, 'buf, T>(
buf.spaces(separator_spaces);
buf.push('?');
buf.spaces(1);
ann.value.format(buf, indent);
}
LabelOnly(name) => {

View file

@ -38,7 +38,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
let braces_indent = indent;
let item_indent = braces_indent + INDENT;
if newline == Newlines::Yes {
buf.newline();
buf.ensure_ends_with_newline();
}
buf.indent(braces_indent);
buf.push(start);

View file

@ -61,7 +61,7 @@ impl<'a> Formattable for TypeDef<'a> {
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::TypeDef::*;
@ -76,8 +76,19 @@ impl<'a> Formattable for TypeDef<'a> {
for var in *vars {
buf.spaces(1);
let need_parens = matches!(var.value, Pattern::Apply(..));
if need_parens {
buf.push_str("(");
}
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
if need_parens {
buf.push_str(")");
}
}
buf.push_str(" :");
@ -86,22 +97,10 @@ impl<'a> Formattable for TypeDef<'a> {
ann.format(buf, indent)
}
Opaque {
header: TypeHeader { name, vars },
header,
typ: ann,
derived: has_abilities,
} => {
buf.indent(indent);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
}
buf.push_str(" :=");
buf.spaces(1);
let ann_is_where_clause =
matches!(ann.extract_spaces().item, TypeAnnotation::Where(..));
@ -115,7 +114,7 @@ impl<'a> Formattable for TypeDef<'a> {
let make_multiline = ann.is_multiline() || has_abilities_multiline;
ann.format(buf, indent);
fmt_general_def(header, buf, indent, ":=", &ann.value, newlines);
if let Some(has_abilities) = has_abilities {
buf.spaces(1);
@ -167,6 +166,29 @@ impl<'a> Formattable for TypeDef<'a> {
}
}
impl<'a> Formattable for TypeHeader<'a> {
fn is_multiline(&self) -> bool {
self.vars.iter().any(|v| v.is_multiline())
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
buf.push_str(self.name.value);
for var in self.vars.iter() {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
}
}
}
impl<'a> Formattable for ValueDef<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::ValueDef::*;
@ -193,63 +215,14 @@ impl<'a> Formattable for ValueDef<'a> {
use roc_parse::ast::ValueDef::*;
match self {
Annotation(loc_pattern, loc_annotation) => {
loc_pattern.format(buf, indent);
buf.indent(indent);
if loc_annotation.is_multiline() {
buf.push_str(" :");
buf.spaces(1);
let should_outdent = match loc_annotation.value {
TypeAnnotation::SpaceBefore(sub_def, spaces) => match sub_def {
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
is_only_newlines && sub_def.is_multiline()
}
_ => false,
},
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => true,
_ => false,
};
if should_outdent {
match loc_annotation.value {
TypeAnnotation::SpaceBefore(sub_def, _) => {
sub_def.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
_ => {
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
}
} else {
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
newlines,
indent + INDENT,
);
}
} else {
buf.spaces(1);
buf.push(':');
buf.spaces(1);
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
fmt_general_def(
loc_pattern,
buf,
indent,
":",
&loc_annotation.value,
newlines,
);
}
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
@ -266,34 +239,7 @@ impl<'a> Formattable for ValueDef<'a> {
body_pattern,
body_expr,
} => {
let is_type_multiline = ann_type.is_multiline();
let is_type_function = matches!(
ann_type.value,
TypeAnnotation::Function(..)
| TypeAnnotation::SpaceBefore(TypeAnnotation::Function(..), ..)
| TypeAnnotation::SpaceAfter(TypeAnnotation::Function(..), ..)
);
let next_indent = if is_type_multiline {
indent + INDENT
} else {
indent
};
ann_pattern.format(buf, indent);
buf.push_str(" :");
if is_type_multiline && is_type_function {
ann_type.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
next_indent,
);
} else {
buf.spaces(1);
ann_type.format(buf, indent);
}
fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines);
if let Some(comment_str) = comment {
buf.push_str(" #");
@ -308,6 +254,66 @@ impl<'a> Formattable for ValueDef<'a> {
}
}
fn fmt_general_def<L: Formattable>(
lhs: L,
buf: &mut Buf,
indent: u16,
sep: &str,
rhs: &TypeAnnotation,
newlines: Newlines,
) {
lhs.format(buf, indent);
buf.indent(indent);
if rhs.is_multiline() {
buf.spaces(1);
buf.push_str(sep);
buf.spaces(1);
let should_outdent = should_outdent(rhs);
if should_outdent {
match rhs {
TypeAnnotation::SpaceBefore(sub_def, _) => {
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
_ => {
rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
} else {
rhs.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT);
}
} else {
buf.spaces(1);
buf.push_str(sep);
buf.spaces(1);
rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
fn should_outdent(mut rhs: &TypeAnnotation) -> bool {
loop {
match rhs {
TypeAnnotation::SpaceBefore(sub_def, spaces) => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
if !is_only_newlines || !sub_def.is_multiline() {
return false;
}
rhs = sub_def;
}
TypeAnnotation::Where(ann, _clauses) => {
if !ann.is_multiline() {
return false;
}
rhs = &ann.value;
}
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => return true,
_ => return false,
}
}
}
fn fmt_dbg_in_def<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,

View file

@ -1,4 +1,4 @@
use crate::annotation::{except_last, Formattable, Newlines, Parens};
use crate::annotation::{except_last, is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::def::fmt_defs;
use crate::pattern::fmt_pattern;
@ -12,6 +12,7 @@ use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch,
};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::ident::Accessor;
use roc_region::all::Loc;
impl<'a> Formattable for Expr<'a> {
@ -35,9 +36,8 @@ impl<'a> Formattable for Expr<'a> {
| NonBase10Int { .. }
| SingleQuote(_)
| RecordAccess(_, _)
| RecordAccessorFunction(_)
| AccessorFunction(_)
| TupleAccess(_, _)
| TupleAccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)
@ -49,27 +49,9 @@ impl<'a> Formattable for Expr<'a> {
// These expressions always have newlines
Defs(_, _) | When(_, _) => true,
List(items) => items.iter().any(|loc_expr| loc_expr.is_multiline()),
List(items) => is_collection_multiline(items),
Str(literal) => {
use roc_parse::ast::StrLiteral::*;
match literal {
PlainLine(string) => {
// When a PlainLine contains '\n' or '"', format as a block string
string.contains('"') || string.contains('\n')
}
Line(_) => {
// If this had any newlines, it'd have parsed as Block.
false
}
Block(_) => {
// Block strings are always formatted on multiple lines,
// even if the string is only a single line.
true
}
}
}
Str(literal) => is_str_multiline(literal),
Apply(loc_expr, args, _) => {
loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline())
}
@ -114,9 +96,9 @@ impl<'a> Formattable for Expr<'a> {
.any(|loc_pattern| loc_pattern.is_multiline())
}
Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
Tuple(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
Record(fields) => is_collection_multiline(fields),
Tuple(fields) => is_collection_multiline(fields),
RecordUpdate { fields, .. } => is_collection_multiline(fields),
}
}
@ -271,8 +253,21 @@ impl<'a> Formattable for Expr<'a> {
indent
};
let expr_needs_parens =
matches!(loc_expr.value.extract_spaces().item, Expr::Closure(..))
&& !loc_args.is_empty();
if expr_needs_parens {
buf.push('(');
}
loc_expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
if expr_needs_parens {
buf.indent(indent);
buf.push(')');
}
for loc_arg in loc_args.iter() {
if should_reflow_outdentable {
buf.spaces(1);
@ -432,23 +427,45 @@ impl<'a> Formattable for Expr<'a> {
}
}
sub_expr.format_with_options(buf, Parens::InApply, newlines, indent);
let needs_newline = match &sub_expr.value {
SpaceBefore(..) => true,
Str(text) => is_str_multiline(text),
_ => false,
};
let needs_parens =
needs_newline && matches!(unary_op.value, called_via::UnaryOp::Negate);
if needs_parens {
// Unary negation can't be followed by whitespace (which is what a newline is) - so
// we need to wrap the negated value in parens.
buf.push('(');
}
let inner_indent = if needs_parens {
indent + INDENT
} else {
indent
};
sub_expr.format_with_options(buf, Parens::InApply, newlines, inner_indent);
if needs_parens {
buf.push(')');
}
}
RecordAccessorFunction(key) => {
AccessorFunction(key) => {
buf.indent(indent);
buf.push('.');
buf.push_str(key);
match key {
Accessor::RecordField(key) => buf.push_str(key),
Accessor::TupleIndex(key) => buf.push_str(key),
}
}
RecordAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
buf.push_str(key);
}
TupleAccessorFunction(key) => {
buf.indent(indent);
buf.push('.');
buf.push_str(key);
}
TupleAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
@ -464,6 +481,26 @@ impl<'a> Formattable for Expr<'a> {
}
}
fn is_str_multiline(literal: &StrLiteral) -> bool {
use roc_parse::ast::StrLiteral::*;
match literal {
PlainLine(string) => {
// When a PlainLine contains '\n' or '"', format as a block string
string.contains('"') || string.contains('\n')
}
Line(_) => {
// If this had any newlines, it'd have parsed as Block.
false
}
Block(_) => {
// Block strings are always formatted on multiple lines,
// even if the string is only a single line.
true
}
}
}
fn needs_unicode_escape(ch: char) -> bool {
matches!(ch, '\u{0000}'..='\u{001f}' | '\u{007f}'..='\u{009f}')
}
@ -585,11 +622,11 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("\"\"\"");
buf.newline();
buf.push_newline_literal();
for line in string.split('\n') {
buf.indent(indent);
buf.push_str_allow_spaces(line);
buf.newline();
buf.push_newline_literal();
}
buf.indent(indent);
buf.push_str("\"\"\"");
@ -613,7 +650,7 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("\"\"\"");
buf.newline();
buf.push_newline_literal();
for segments in lines.iter() {
for seg in segments.iter() {
@ -622,11 +659,11 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.indent(indent);
format_str_segment(seg, buf, indent);
} else {
buf.newline();
buf.push_newline_literal();
}
}
buf.newline();
buf.push_newline_literal();
}
buf.indent(indent);
buf.push_str("\"\"\"");
@ -1282,7 +1319,7 @@ fn fmt_record<'a, 'buf>(
let loc_fields = fields.items;
let final_comments = fields.final_comments();
buf.indent(indent);
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) && update.is_none() {
buf.push_str("{}");
} else {
buf.push('{');

View file

@ -106,12 +106,20 @@ impl<'a> Buf<'a> {
self.spaces_to_flush += count;
}
pub fn newline(&mut self) {
/// Only for use in emitting newlines in block strings, which don't follow the rule of
/// having at most two newlines in a row.
pub fn push_newline_literal(&mut self) {
self.spaces_to_flush = 0;
self.newlines_to_flush += 1;
self.beginning_of_line = true;
}
pub fn newline(&mut self) {
self.spaces_to_flush = 0;
self.newlines_to_flush = std::cmp::min(self.newlines_to_flush + 1, 2);
self.beginning_of_line = true;
}
/// Ensures the current buffer ends in a newline, if it didn't already.
/// Doesn't add a newline if the buffer already ends in one.
pub fn ensure_ends_with_newline(&mut self) {

View file

@ -656,9 +656,8 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::RecordAccessorFunction(a) => Expr::RecordAccessorFunction(a),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::TupleAccessorFunction(a) => Expr::TupleAccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
@ -745,6 +744,7 @@ fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
BadIdent::WeirdDotQualified(_) => BadIdent::WeirdDotQualified(Position::zero()),
BadIdent::StrayDot(_) => BadIdent::StrayDot(Position::zero()),
BadIdent::BadOpaqueRef(_) => BadIdent::BadOpaqueRef(Position::zero()),
BadIdent::QualifiedTupleAccessor(_) => BadIdent::QualifiedTupleAccessor(Position::zero()),
}
}
@ -813,8 +813,8 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
vars: vars.remove_spaces(arena),
},
),
TypeAnnotation::Tuple { fields, ext } => TypeAnnotation::Tuple {
fields: fields.remove_spaces(arena),
TypeAnnotation::Tuple { elems: fields, ext } => TypeAnnotation::Tuple {
elems: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {

View file

@ -1,28 +1,29 @@
[package]
name = "roc_gen_dev"
description = "The development backend for the Roc compiler"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
bumpalo.workspace = true
target-lexicon.workspace = true
object.workspace = true
packed_struct.workspace = true
target-lexicon.workspace = true
[dev-dependencies]
roc_can = { path = "../can" }

View file

@ -86,6 +86,57 @@ This is the general procedure I follow with some helpful links:
1. If things aren't working, reach out on zulip. Get advice, maybe even pair.
1. Make a PR.
## Debugging x86_64 backend output
While working on the x86_64 backend it may be useful to inspect the assembly output of a given piece of Roc code. With the right tools, you can do this rather easily. You'll need `objdump` to follow along.
We'll try to explore the x86 assembly output of some lines of Roc code:
```elixir
app "dbg"
provides [main] to "."
main =
(List.len [1]) + 41
```
If this file exists somewhere in the repo as `dbg.roc`, we'll be able to compile an object file by issuing the following command:
```console
# `cargo run --` can be replaces with calling the compiled `roc` cli binary.
$ cargo run -- build --dev main.roc --no-link
```
Which will produce a minimal `dbg.o` object file containing the output assembly code. This object file can be inspected by using `objdump` in the following way:
```console
$ objdump -M intel -dS dbg.o
dbg.o: file format elf64-x86-64
Disassembly of section .text.700000006:
0000000000000000 <List_len_1>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 8b 85 18 00 00 00 mov rax,QWORD PTR [rbp+0x18]
b: 5d pop rbp
c: c3 ret
Disassembly of section .text.400000013:
0000000000000000 <Num_add_1>:
0: 55 push rbp
# .. more output ..
Disassembly of section .text.1000000000:
0000000000000000 <roc__main_1_exposed>:
0: 55 push rbp
# .. more output ..
```
The output lines contain the hexadecimal representation of the x86 opcodes and fields followed by the `intel` assembly syntax. This setup is very useful for figuring out the causes of invalid pointer references (or equivalent) when running the resulting x86 assembly.
## Helpful Resources
- [Compiler Explorer](https://godbolt.org/) -
@ -105,10 +156,10 @@ This is the general procedure I follow with some helpful links:
Also, sometimes it doesn't seem to generate things quite as you expect.
- [Alternative Online Assembler](http://shell-storm.org/online/Online-Assembler-and-Disassembler/) -
Like previous but with more architecture options.
- [x86 and amd64 instruction reference](https://www.felixcloutier.com/x86/) -
- [x86 and amd64 instruction reference](https://web.archive.org/web/20230221053750/https://www.felixcloutier.com/x86/) -
Great for looking up x86_64 instructions and there bytes.
Definitely missing information if you aren't used to reading it.
- [Intel 64 ISA Reference](https://software.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf) -
- [Intel 64 ISA Reference](https://community.intel.com/legacyfs/online/drupal_files/managed/a4/60/325383-sdm-vol-2abcd.pdf) -
Super dense manual.
Contains everything you would need to know for x86_64.
Also is like 2000 pages.

View file

@ -2,10 +2,13 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::Relocation;
use bumpalo::collections::Vec;
use packed_struct::prelude::*;
use roc_builtins::bitcode::FloatWidth;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{InLayout, STLayoutInterner};
use super::CompareOperation;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[allow(dead_code)]
pub enum AArch64GeneralReg {
@ -609,9 +612,31 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
}
}
#[inline(always)]
fn mov_reg32_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
todo!()
}
#[inline(always)]
fn mov_reg16_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
todo!()
}
#[inline(always)]
fn mov_reg8_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
todo!()
}
#[inline(always)]
fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
todo!("saving floating point reg to base offset for AArch64");
}
#[inline(always)]
fn movesd_mem64_offset32_freg64(
_buf: &mut Vec<'_, u8>,
_ptr: AArch64GeneralReg,
_offset: i32,
_src: AArch64FloatReg,
) {
todo!()
}
#[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
if offset < 0 {
@ -624,6 +649,19 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
}
}
#[inline(always)]
fn mov_base32_reg32(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
todo!()
}
#[inline(always)]
fn mov_base32_reg16(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
todo!()
}
#[inline(always)]
fn mov_base32_reg8(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
todo!()
}
#[inline(always)]
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
@ -640,6 +678,41 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("mem offsets over 32k for AArch64");
}
}
#[inline(always)]
fn mov_reg32_mem32_offset32(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
src: AArch64GeneralReg,
offset: i32,
) {
if offset < 0 {
todo!("negative mem offsets for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
ldr_reg64_reg64_imm12(buf, dst, src, (offset as u16) >> 3);
} else {
todo!("mem offsets over 32k for AArch64");
}
}
#[inline(always)]
fn mov_reg16_mem16_offset32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src: AArch64GeneralReg,
_offset: i32,
) {
todo!()
}
#[inline(always)]
fn mov_reg8_mem8_offset32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src: AArch64GeneralReg,
_offset: i32,
) {
todo!()
}
#[inline(always)]
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
@ -657,6 +730,36 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
}
}
#[inline(always)]
fn mov_mem32_offset32_reg32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_offset: i32,
_src: AArch64GeneralReg,
) {
todo!()
}
#[inline(always)]
fn mov_mem16_offset32_reg16(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_offset: i32,
_src: AArch64GeneralReg,
) {
todo!()
}
#[inline(always)]
fn mov_mem8_offset32_reg8(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_offset: i32,
_src: AArch64GeneralReg,
) {
todo!()
}
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
@ -740,12 +843,12 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
}
#[inline(always)]
fn sub_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
src1: AArch64GeneralReg,
src2: AArch64GeneralReg,
) {
todo!("registers subtractions for AArch64");
sub_reg64_reg64_reg64(buf, dst, src1, src2);
}
#[inline(always)]
@ -788,6 +891,18 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("registers unsigned less than for AArch64");
}
#[inline(always)]
fn cmp_freg_freg_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
_width: FloatWidth,
_operation: CompareOperation,
) {
todo!("registers float comparison for AArch64");
}
#[inline(always)]
fn igt_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
@ -899,6 +1014,53 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
) {
todo!("bitwise xor for AArch64")
}
fn shl_reg64_reg64_reg64<'a, 'r, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("shl for AArch64")
}
fn shr_reg64_reg64_reg64<'a, 'r, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("shr for AArch64")
}
fn sar_reg64_reg64_reg64<'a, 'r, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("sar for AArch64")
}
fn sqrt_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
todo!("sqrt")
}
fn sqrt_freg32_freg32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
todo!("sqrt")
}
}
impl AArch64Assembler {}
@ -1315,6 +1477,19 @@ fn sub_reg64_reg64_imm12(
buf.extend(inst.bytes());
}
/// `SUB Xd, Xm, Xn` -> Subtract Xm and Xn and place the result into Xd.
#[inline(always)]
fn sub_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
src1: AArch64GeneralReg,
src2: AArch64GeneralReg,
) {
let inst = ArithmeticShifted::new(true, false, ShiftType::LSL, 0, src2, src1, dst);
buf.extend(inst.bytes());
}
/// `RET Xn` -> Return to the address stored in Xn.
#[inline(always)]
fn ret_reg64(buf: &mut Vec<'_, u8>, xn: AArch64GeneralReg) {
@ -1535,6 +1710,34 @@ mod tests {
);
}
#[test]
fn test_sub_reg64_reg64_reg64() {
disassembler_test!(
sub_reg64_reg64_reg64,
|reg1: AArch64GeneralReg, reg2: AArch64GeneralReg, reg3: AArch64GeneralReg| {
if reg2 == AArch64GeneralReg::ZRSP {
// When the second register is ZR, it gets disassembled as neg,
// which is an alias for sub.
format!(
"neg {}, {}",
reg1.capstone_string(UsesZR),
reg3.capstone_string(UsesZR)
)
} else {
format!(
"sub {}, {}, {}",
reg1.capstone_string(UsesZR),
reg2.capstone_string(UsesZR),
reg3.capstone_string(UsesZR)
)
}
},
ALL_GENERAL_REGS,
ALL_GENERAL_REGS,
ALL_GENERAL_REGS
);
}
#[test]
fn test_ret_reg64() {
disassembler_test!(

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@ use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::{
borrow::Ownership,
ir::{JoinPointId, Param},
layout::{
Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
@ -315,7 +314,7 @@ impl<
reg: Some(Float(_)),
..
}) => {
internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym)
internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}")
}
Stack(Primitive {
reg: None,
@ -350,8 +349,10 @@ impl<
self.free_reference(sym);
reg
}
Stack(Complex { .. }) => {
internal_error!("Cannot load large values into general registers: {}", sym)
Stack(Complex { size, .. }) => {
internal_error!(
"Cannot load large values (size {size}) into general registers: {sym:?}",
)
}
NoData => {
internal_error!("Cannot load no data into general registers: {}", sym)
@ -448,7 +449,7 @@ impl<
reg: Some(Float(_)),
..
}) => {
internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym)
internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}",)
}
Stack(Primitive {
reg: None,
@ -458,19 +459,25 @@ impl<
ASM::mov_reg64_base32(buf, reg, *base_offset);
}
Stack(ReferencedPrimitive {
base_offset, size, ..
}) if base_offset % 8 == 0 && *size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_reg64_base32(buf, reg, *base_offset);
base_offset,
size,
sign_extend,
}) => {
debug_assert!(*size <= 8);
if *sign_extend {
ASM::movsx_reg64_base32(buf, reg, *base_offset, *size as u8)
} else {
ASM::movzx_reg64_base32(buf, reg, *base_offset, *size as u8)
}
}
Stack(ReferencedPrimitive { .. }) => {
todo!("loading referenced primitives")
}
Stack(Complex { .. }) => {
internal_error!("Cannot load large values into general registers: {}", sym)
Stack(Complex { size, .. }) => {
internal_error!(
"Cannot load large values (size {size}) into general registers: {sym:?}",
)
}
NoData => {
internal_error!("Cannot load no data into general registers: {}", sym)
internal_error!("Cannot load no data into general registers: {:?}", sym)
}
}
}
@ -553,7 +560,7 @@ impl<
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert(
*sym,
Stack(if is_primitive(layout) {
Stack(if is_primitive(layout_interner, layout) {
ReferencedPrimitive {
base_offset: data_offset,
size,
@ -739,15 +746,73 @@ impl<
layout: &InLayout<'a>,
) {
match layout_interner.get(*layout) {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
Layout::Builtin(builtin) => match builtin {
Builtin::Int(int_width) => match int_width {
IntWidth::I128 | IntWidth::U128 => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert_eq!(from_offset % 8, 0);
debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
IntWidth::I64 | IntWidth::U64 => {
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, to_offset, reg);
}
IntWidth::I32 | IntWidth::U32 => {
debug_assert_eq!(to_offset % 4, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg32(buf, to_offset, reg);
}
IntWidth::I16 | IntWidth::U16 => {
debug_assert_eq!(to_offset % 2, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg16(buf, to_offset, reg);
}
IntWidth::I8 | IntWidth::U8 => {
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg8(buf, to_offset, reg);
}
},
Builtin::Float(float_width) => match float_width {
FloatWidth::F64 => {
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg);
}
FloatWidth::F32 => todo!(),
},
Builtin::Bool => {
// same as 8-bit integer
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg8(buf, to_offset, reg);
}
Builtin::Decimal => todo!(),
Builtin::Str | Builtin::List(_) => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert_eq!(from_offset % 8, 0);
debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
},
Layout::Boxed(_) => {
// like a 64-bit integer
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, to_offset, reg);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg);
Layout::LambdaSet(lambda_set) => {
// like its runtime representation
self.copy_symbol_to_stack_offset(
layout_interner,
buf,
to_offset,
sym,
&lambda_set.runtime_representation(),
)
}
_ if layout_interner.stack_size(*layout) == 0 => {}
// TODO: Verify this is always true.
@ -756,20 +821,64 @@ impl<
// Later, it will be reloaded and stored in refcounted as needed.
_ if layout_interner.stack_size(*layout) > 8 => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(from_offset % 8 == 0);
debug_assert!(size % 8 == 0);
debug_assert_eq!(from_offset % 8, 0);
debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + i);
ASM::mov_base32_reg64(buf, to_offset + i, reg);
}
});
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
x => todo!("copying data to the stack with layout, {:?}", x),
}
}
pub fn copy_to_stack_offset(
&mut self,
buf: &mut Vec<'a, u8>,
size: u32,
from_offset: i32,
to_offset: i32,
) {
let mut copied = 0;
let size = size as i32;
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
if size - copied >= 8 {
for _ in (0..(size - copied)).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg64(buf, to_offset + copied, reg);
copied += 8;
}
}
if size - copied >= 4 {
for _ in (0..(size - copied)).step_by(4) {
ASM::mov_reg32_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg32(buf, to_offset + copied, reg);
copied += 4;
}
}
if size - copied >= 2 {
for _ in (0..(size - copied)).step_by(2) {
ASM::mov_reg16_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg16(buf, to_offset + copied, reg);
copied += 2;
}
}
if size - copied >= 1 {
for _ in (0..(size - copied)).step_by(1) {
ASM::mov_reg8_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg8(buf, to_offset + copied, reg);
copied += 1;
}
}
});
}
#[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free.
pub fn ensure_reg_free(
@ -928,7 +1037,7 @@ impl<
) => (*base_offset, *size),
storage => {
internal_error!(
"Data not on the stack for sym ({}) with storage ({:?})",
"Data not on the stack for sym {:?} with storage {:?}",
sym,
storage
)
@ -1008,15 +1117,10 @@ impl<
param_storage.reserve(params.len());
for Param {
symbol,
ownership,
ownership: _,
layout,
} in params
{
if *ownership == Ownership::Borrowed {
// These probably need to be passed by pointer/reference?
// Otherwise, we probably need to copy back to the param at the end of the joinpoint.
todo!("joinpoints with borrowed parameters");
}
// Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity.
match *layout {
@ -1331,6 +1435,15 @@ impl<
}
}
fn is_primitive(layout: InLayout<'_>) -> bool {
matches!(layout, single_register_layouts!())
fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool {
match layout {
single_register_layouts!() => true,
_ => match layout_interner.get(layout) {
Layout::Boxed(_) => true,
Layout::LambdaSet(lambda_set) => {
is_primitive(layout_interner, lambda_set.runtime_representation())
}
_ => false,
},
}
}

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@ use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutId, LayoutIds, LayoutInterner, STLayoutInterner, TagIdIntType,
UnionLayout,
};
use roc_mono::list_element_layout;
mod generic64;
mod object_builder;
@ -283,28 +284,31 @@ trait Backend<'a> {
if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
LowLevelWrapperType::from_symbol(func_sym.name())
{
self.build_run_low_level(
return self.build_run_low_level(
sym,
&lowlevel,
arguments,
arg_layouts,
ret_layout,
)
} else if self.defined_in_app_module(func_sym.name()) {
let layout_id = LayoutIds::default().get(func_sym.name(), layout);
let fn_name = self.symbol_to_string(func_sym.name(), layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(arguments);
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
} else {
self.build_builtin(
);
} else if sym.is_builtin() {
// These builtins can be built through `build_fn_call` as well, but the
// implementation in `build_builtin` inlines some of the symbols.
return self.build_builtin(
sym,
func_sym.name(),
arguments,
arg_layouts,
ret_layout,
)
);
}
let layout_id = LayoutIds::default().get(func_sym.name(), layout);
let fn_name = self.symbol_to_string(func_sym.name(), layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(arguments);
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
}
CallType::LowLevel { op: lowlevel, .. } => {
@ -380,6 +384,21 @@ trait Backend<'a> {
self.load_literal_symbols(arguments);
self.tag(sym, arguments, tag_layout, *tag_id);
}
Expr::ExprBox { symbol: value } => {
let element_layout = match self.interner().get(*layout) {
Layout::Boxed(boxed) => boxed,
_ => unreachable!("{:?}", self.interner().dbg(*layout)),
};
self.load_literal_symbols([*value].as_slice());
self.expr_box(*sym, *value, element_layout)
}
Expr::ExprUnbox { symbol: ptr } => {
let element_layout = *layout;
self.load_literal_symbols([*ptr].as_slice());
self.expr_unbox(*sym, *ptr, element_layout)
}
x => todo!("the expression, {:?}", x),
}
}
@ -428,6 +447,9 @@ trait Backend<'a> {
LowLevel::NumAddChecked => {
self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
}
LowLevel::NumSubChecked => {
self.build_num_sub_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
}
LowLevel::NumAcos => self.build_fn_call(
sym,
bitcode::NUM_ACOS[FloatWidth::F64].to_string(),
@ -532,6 +554,27 @@ trait Backend<'a> {
);
self.build_num_sub_wrap(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumSubSaturated => match self.interner().get(*ret_layout) {
Layout::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
sym,
bitcode::NUM_SUB_SATURATED_INT[int_width].to_string(),
args,
arg_layouts,
ret_layout,
),
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
self.build_num_sub(sym, &args[0], &args[1], ret_layout)
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
// saturated sub is just normal sub
self.build_num_sub(sym, &args[0], &args[1], ret_layout)
}
Layout::Builtin(Builtin::Decimal) => {
// self.load_args_and_call_zig(backend, bitcode::DEC_SUB_SATURATED)
todo!()
}
_ => internal_error!("invalid return type"),
},
LowLevel::NumBitwiseAnd => {
if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
self.build_int_bitwise_and(sym, &args[0], &args[1], int_width)
@ -553,6 +596,41 @@ trait Backend<'a> {
internal_error!("bitwise xor on a non-integer")
}
}
LowLevel::And => {
if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) {
self.build_int_bitwise_and(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise and on a non-integer")
}
}
LowLevel::Or => {
if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) {
self.build_int_bitwise_or(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise or on a non-integer")
}
}
LowLevel::NumShiftLeftBy => {
if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
self.build_int_shift_left(sym, &args[0], &args[1], int_width)
} else {
internal_error!("shift left on a non-integer")
}
}
LowLevel::NumShiftRightBy => {
if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
self.build_int_shift_right(sym, &args[0], &args[1], int_width)
} else {
internal_error!("shift right on a non-integer")
}
}
LowLevel::NumShiftRightZfBy => {
if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
self.build_int_shift_right_zero_fill(sym, &args[0], &args[1], int_width)
} else {
internal_error!("shift right zero-fill on a non-integer")
}
}
LowLevel::Eq => {
debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument");
debug_assert_eq!(
@ -583,6 +661,15 @@ trait Backend<'a> {
);
self.build_neq(sym, &args[0], &args[1], &arg_layouts[0])
}
LowLevel::Not => {
debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument");
debug_assert_eq!(
Layout::BOOL,
*ret_layout,
"Not: expected to have return layout of type Bool"
);
self.build_not(sym, &args[0], &arg_layouts[0])
}
LowLevel::NumLt => {
debug_assert_eq!(
2,
@ -664,6 +751,30 @@ trait Backend<'a> {
);
self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0])
}
LowLevel::NumLogUnchecked => {
let float_width = match arg_layouts[0] {
Layout::F64 => FloatWidth::F64,
Layout::F32 => FloatWidth::F32,
_ => unreachable!("invalid layout for sqrt"),
};
self.build_fn_call(
sym,
bitcode::NUM_LOG[float_width].to_string(),
args,
arg_layouts,
ret_layout,
)
}
LowLevel::NumSqrtUnchecked => {
let float_width = match arg_layouts[0] {
Layout::F64 => FloatWidth::F64,
Layout::F32 => FloatWidth::F32,
_ => unreachable!("invalid layout for sqrt"),
};
self.build_num_sqrt(*sym, args[0], float_width);
}
LowLevel::NumRound => self.build_fn_call(
sym,
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
@ -685,7 +796,24 @@ trait Backend<'a> {
args.len(),
"ListWithCapacity: expected to have exactly one argument"
);
self.build_list_with_capacity(sym, args[0], arg_layouts[0], ret_layout)
let elem_layout = list_element_layout!(self.interner(), *ret_layout);
self.build_list_with_capacity(sym, args[0], arg_layouts[0], elem_layout, ret_layout)
}
LowLevel::ListReserve => {
debug_assert_eq!(
2,
args.len(),
"ListReserve: expected to have exactly two arguments"
);
self.build_list_reserve(sym, args, arg_layouts, ret_layout)
}
LowLevel::ListAppendUnsafe => {
debug_assert_eq!(
2,
args.len(),
"ListAppendUnsafe: expected to have exactly two arguments"
);
self.build_list_append_unsafe(sym, args, arg_layouts, ret_layout)
}
LowLevel::ListGetUnsafe => {
debug_assert_eq!(
@ -703,6 +831,23 @@ trait Backend<'a> {
);
self.build_list_replace_unsafe(sym, args, arg_layouts, ret_layout)
}
LowLevel::ListConcat => {
debug_assert_eq!(
2,
args.len(),
"ListConcat: expected to have exactly two arguments"
);
let elem_layout = list_element_layout!(self.interner(), *ret_layout);
self.build_list_concat(sym, args, arg_layouts, elem_layout, ret_layout)
}
LowLevel::ListPrepend => {
debug_assert_eq!(
2,
args.len(),
"ListPrepend: expected to have exactly two arguments"
);
self.build_list_prepend(sym, args, arg_layouts, ret_layout)
}
LowLevel::StrConcat => self.build_fn_call(
sym,
bitcode::STR_CONCAT.to_string(),
@ -710,6 +855,171 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::StrJoinWith => self.build_fn_call(
sym,
bitcode::STR_JOIN_WITH.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrSplit => self.build_fn_call(
sym,
bitcode::STR_SPLIT.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrStartsWith => self.build_fn_call(
sym,
bitcode::STR_STARTS_WITH.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrStartsWithScalar => self.build_fn_call(
sym,
bitcode::STR_STARTS_WITH_SCALAR.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrAppendScalar => self.build_fn_call(
sym,
bitcode::STR_APPEND_SCALAR.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrEndsWith => self.build_fn_call(
sym,
bitcode::STR_ENDS_WITH.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrCountGraphemes => self.build_fn_call(
sym,
bitcode::STR_COUNT_GRAPEHEME_CLUSTERS.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrSubstringUnsafe => self.build_fn_call(
sym,
bitcode::STR_SUBSTRING_UNSAFE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrToUtf8 => self.build_fn_call(
sym,
bitcode::STR_TO_UTF8.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrCountUtf8Bytes => self.build_fn_call(
sym,
bitcode::STR_COUNT_UTF8_BYTES.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrFromUtf8Range => self.build_fn_call(
sym,
bitcode::STR_FROM_UTF8_RANGE.to_string(),
args,
arg_layouts,
ret_layout,
),
// LowLevel::StrToUtf8 => self.build_fn_call(
// sym,
// bitcode::STR_TO_UTF8.to_string(),
// args,
// arg_layouts,
// ret_layout,
// ),
LowLevel::StrRepeat => self.build_fn_call(
sym,
bitcode::STR_REPEAT.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrTrim => self.build_fn_call(
sym,
bitcode::STR_TRIM.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrTrimLeft => self.build_fn_call(
sym,
bitcode::STR_TRIM_LEFT.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrTrimRight => self.build_fn_call(
sym,
bitcode::STR_TRIM_RIGHT.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrReserve => self.build_fn_call(
sym,
bitcode::STR_RESERVE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrWithCapacity => self.build_fn_call(
sym,
bitcode::STR_WITH_CAPACITY.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrToScalars => self.build_fn_call(
sym,
bitcode::STR_TO_SCALARS.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrGetUnsafe => self.build_fn_call(
sym,
bitcode::STR_GET_UNSAFE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrGetScalarUnsafe => self.build_fn_call(
sym,
bitcode::STR_GET_SCALAR_UNSAFE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrToNum => {
let number_layout = match self.interner().get(*ret_layout) {
Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct?
_ => unreachable!(),
};
// match on the return layout to figure out which zig builtin we need
let intrinsic = match self.interner().get(number_layout) {
Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width],
Layout::Builtin(Builtin::Float(float_width)) => {
&bitcode::STR_TO_FLOAT[float_width]
}
Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR,
_ => unreachable!(),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::PtrCast => {
debug_assert_eq!(
1,
@ -746,7 +1056,6 @@ trait Backend<'a> {
arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
) {
self.load_literal_symbols(args);
match func_sym {
Symbol::NUM_IS_ZERO => {
debug_assert_eq!(
@ -760,6 +1069,7 @@ trait Backend<'a> {
"NumIsZero: expected to have return layout of type Bool"
);
self.load_literal_symbols(args);
self.load_literal(
&Symbol::DEV_TMP,
&arg_layouts[0],
@ -768,7 +1078,7 @@ trait Backend<'a> {
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP)
}
Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => {
Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE | Symbol::LIST_APPEND => {
// TODO: This is probably simple enough to be worth inlining.
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
@ -776,24 +1086,36 @@ trait Backend<'a> {
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
Symbol::NUM_ADD_CHECKED => {
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
Symbol::BOOL_TRUE => {
let bool_layout = Layout::BOOL;
self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(true));
self.return_symbol(&Symbol::DEV_TMP, &bool_layout);
self.free_symbol(&Symbol::DEV_TMP)
}
Symbol::BOOL_FALSE => {
let bool_layout = Layout::BOOL;
self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(false));
self.return_symbol(&Symbol::DEV_TMP, &bool_layout);
self.free_symbol(&Symbol::DEV_TMP)
}
Symbol::STR_IS_VALID_SCALAR => {
// just call the function
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
other => {
eprintln!("maybe {other:?} should have a custom implementation?");
// just call the function
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
_ => todo!("the function, {:?}", func_sym),
}
}
@ -824,6 +1146,16 @@ trait Backend<'a> {
return_layout: &InLayout<'a>,
);
/// build_num_sub_checked stores the sum of src1 and src2 into dst.
fn build_num_sub_checked(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
num_layout: &InLayout<'a>,
return_layout: &InLayout<'a>,
);
/// build_num_mul stores `src1 * src2` into dst.
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>);
@ -872,12 +1204,42 @@ trait Backend<'a> {
int_width: IntWidth,
);
/// stores the `Num.shiftLeftBy src1 src2` into dst.
fn build_int_shift_left(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
);
/// stores the `Num.shiftRightBy src1 src2` into dst.
fn build_int_shift_right(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
);
/// stores the `Num.shiftRightZfBy src1 src2` into dst.
fn build_int_shift_right_zero_fill(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
);
/// build_eq stores the result of `src1 == src2` into dst.
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>);
/// build_neq stores the result of `src1 != src2` into dst.
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>);
/// build_not stores the result of `!src` into dst.
fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>);
/// build_num_lt stores the result of `src1 < src2` into dst.
fn build_num_lt(
&mut self,
@ -923,6 +1285,9 @@ trait Backend<'a> {
arg_layout: &InLayout<'a>,
);
/// build_sqrt stores the result of `sqrt(src)` into dst.
fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth);
/// build_list_len returns the length of a list.
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
@ -932,6 +1297,25 @@ trait Backend<'a> {
dst: &Symbol,
capacity: Symbol,
capacity_layout: InLayout<'a>,
elem_layout: InLayout<'a>,
ret_layout: &InLayout<'a>,
);
/// build_list_reserve enlarges a list to at least accommodate the given capacity.
fn build_list_reserve(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
);
/// build_list_append_unsafe returns a new list with a given element appended.
fn build_list_append_unsafe(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
);
@ -953,6 +1337,25 @@ trait Backend<'a> {
ret_layout: &InLayout<'a>,
);
/// build_list_concat returns a new list containing the two argument lists concatenated.
fn build_list_concat(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[InLayout<'a>],
elem_layout: InLayout<'a>,
ret_layout: &InLayout<'a>,
);
/// build_list_prepend returns a new list with a given element prepended.
fn build_list_prepend(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
);
/// build_refcount_getptr loads the pointer to the reference count of src into dst.
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
@ -1021,6 +1424,12 @@ trait Backend<'a> {
tag_id: TagIdIntType,
);
/// load a value from a pointer
fn expr_unbox(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>);
/// store a refcounted value on the heap
fn expr_box(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>);
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
fn return_symbol(&mut self, sym: &Symbol, layout: &InLayout<'a>);

View file

@ -18,12 +18,16 @@ macro_rules! run_jit_function_raw {
let result = main();
assert_eq!(
$errors,
std::vec::Vec::new(),
"Encountered errors: {:?}",
$errors
);
if !$errors.is_empty() {
dbg!(&$errors);
assert_eq!(
$errors,
std::vec::Vec::new(),
"Encountered errors: {:?}",
$errors
);
}
$transform(result)
}

View file

@ -1,24 +1,26 @@
[package]
name = "roc_gen_llvm"
description = "The LLVM backend for the Roc compiler"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_alias_analysis = { path = "../alias_analysis" }
roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_builtins = { path = "../builtins" }
roc_error_macros = { path = "../../error_macros" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" }
roc_debug_flags = { path = "../debug_flags" }
roc_region = { path = "../region" }
morphic_lib = { path = "../../vendor/morphic_lib" }
roc_alias_analysis = { path = "../alias_analysis" }
roc_bitcode_bc = { path = "../builtins/bitcode/bc" }
roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" }
roc_debug_flags = { path = "../debug_flags" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_region = { path = "../region" }
roc_std = { path = "../../roc_std" }
roc_target = { path = "../roc_target" }
bumpalo.workspace = true
inkwell.workspace = true
target-lexicon.workspace = true
inkwell.workspace = true

View file

@ -11,8 +11,8 @@ use crate::llvm::refcounting::{
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::{BasicType, BasicTypeEnum, StructType};
use inkwell::values::{
BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue,
PointerValue, StructValue,
BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PointerValue,
StructValue,
};
use inkwell::AddressSpace;
use roc_error_macros::internal_error;
@ -206,7 +206,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -244,7 +244,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type =
basic_type_from_layout(env, layout_interner, *layout).ptr_type(AddressSpace::Generic);
basic_type_from_layout(env, layout_interner, *layout).ptr_type(AddressSpace::default());
let cast_ptr = env.builder.build_pointer_cast(
argument_ptr.into_pointer_value(),
@ -274,7 +274,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
}
(true, layout) => {
let closure_type = basic_type_from_layout(env, layout_interner, layout)
.ptr_type(AddressSpace::Generic);
.ptr_type(AddressSpace::default());
let closure_cast =
env.builder
@ -310,8 +310,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
env.builder.build_return(None);
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function_value
}
@ -375,7 +374,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = match rc_operation {
Mode::Inc | Mode::Dec => crate::llvm::refcounting::build_header_help(
@ -411,7 +410,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
generic_value_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout_interner, layout);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
let value_ptr_type = value_type.ptr_type(AddressSpace::default());
let value_ptr =
env.builder
.build_pointer_cast(generic_value_ptr, value_ptr_type, "load_opaque");
@ -449,8 +448,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function_value
}
@ -472,7 +470,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -502,7 +500,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
value_ptr2.set_name(Symbol::ARG_2.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout_interner, layout)
.ptr_type(AddressSpace::Generic);
.ptr_type(AddressSpace::default());
let value_cast1 = env
.builder
@ -533,8 +531,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function_value
}
@ -558,7 +555,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let function_value = match env.module.get_function(fn_name) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -593,7 +590,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
value_ptr2.set_name(Symbol::ARG_3.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout_interner, layout);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
let value_ptr_type = value_type.ptr_type(AddressSpace::default());
let value_cast1 =
env.builder
@ -623,7 +620,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
_ => {
let closure_type =
basic_type_from_layout(env, layout_interner, closure_data_repr);
let closure_ptr_type = closure_type.ptr_type(AddressSpace::Generic);
let closure_ptr_type = closure_type.ptr_type(AddressSpace::default());
let closure_cast = env.builder.build_pointer_cast(
closure_ptr,
@ -659,8 +656,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function_value
}
@ -798,7 +794,7 @@ fn ptr_len_cap<'a, 'ctx, 'env>(
let ptr = env.builder.build_int_to_ptr(
lower_word,
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"list_ptr",
);

View file

@ -24,8 +24,8 @@ use inkwell::types::{
};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{
BasicMetadataValueEnum, BasicValue, CallSiteValue, FunctionValue, InstructionValue, IntValue,
PhiValue, PointerValue, StructValue,
BasicMetadataValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PhiValue,
PointerValue, StructValue,
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
@ -37,11 +37,10 @@ use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{
BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc,
OptLevel, ProcLayout, SingleEntryPoint,
BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet, JoinPointId,
ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint,
};
use roc_mono::layout::{
Builtin, InLayout, LambdaName, LambdaSet, Layout, LayoutIds, LayoutInterner, Niche,
@ -157,7 +156,7 @@ macro_rules! debug_info_init {
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
$env.builder.set_current_debug_location(&$env.context, loc);
$env.builder.set_current_debug_location(loc);
}};
}
@ -201,6 +200,7 @@ pub enum LlvmBackendMode {
BinaryDev,
/// Creates a test wrapper around the main roc function to catch and report panics.
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
BinaryGlue,
GenTest,
WasmGenTest,
CliTest,
@ -211,6 +211,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => true,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => false,
@ -222,6 +223,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => false,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => true,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => true,
@ -232,6 +234,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false,
LlvmBackendMode::CliTest => true,
@ -686,7 +689,7 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx, 'env>(
let output_type = match roc_main_fn.get_type().get_return_type() {
Some(return_type) => {
let output_type = return_type.ptr_type(AddressSpace::Generic);
let output_type = return_type.ptr_type(AddressSpace::default());
output_type.into()
}
None => {
@ -880,7 +883,7 @@ fn small_str_ptr_width_8<'a, 'ctx, 'env>(
let len = env.ptr_int().const_int(word2, false);
let cap = env.ptr_int().const_int(word3, false);
let address_space = AddressSpace::Generic;
let address_space = AddressSpace::default();
let ptr_type = env.context.i8_type().ptr_type(address_space);
let ptr = env.builder.build_int_to_ptr(ptr, ptr_type, "to_u8_ptr");
@ -907,7 +910,7 @@ fn small_str_ptr_width_4<'a, 'ctx, 'env>(
let len = env.ptr_int().const_int(word2 as u64, false);
let cap = env.ptr_int().const_int(word3 as u64, false);
let address_space = AddressSpace::Generic;
let address_space = AddressSpace::default();
let ptr_type = env.context.i8_type().ptr_type(address_space);
let ptr = env.builder.build_int_to_ptr(ptr, ptr_type, "to_u8_ptr");
@ -1049,7 +1052,7 @@ fn struct_pointer_from_fields<'a, 'ctx, 'env, I>(
.builder
.build_bitcast(
input_pointer,
struct_type.ptr_type(AddressSpace::Generic),
struct_type.ptr_type(AddressSpace::default()),
"struct_ptr",
)
.into_pointer_value();
@ -1310,7 +1313,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let data_ptr = env.builder.build_pointer_cast(
opaque_data_ptr,
struct_type.ptr_type(AddressSpace::Generic),
struct_type.ptr_type(AddressSpace::default()),
"to_data_pointer",
);
@ -1338,14 +1341,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let field_layouts = tag_layouts[*tag_id as usize];
let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value());
let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
lookup_at_index_ptr2(
env,
layout_interner,
union_layout,
field_layouts,
*index as usize,
ptr,
target_loaded_type,
)
}
UnionLayout::NonNullableUnwrapped(field_layouts) => {
@ -1353,15 +1357,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let struct_type = basic_type_from_layout(env, layout_interner, struct_layout);
let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
lookup_at_index_ptr(
env,
layout_interner,
union_layout,
field_layouts,
*index as usize,
argument.into_pointer_value(),
struct_type.into_struct_type(),
target_loaded_type,
)
}
UnionLayout::NullableWrapped {
@ -1380,13 +1385,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let field_layouts = other_tags[tag_index as usize];
let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value());
let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
lookup_at_index_ptr2(
env,
layout_interner,
union_layout,
field_layouts,
*index as usize,
ptr,
target_loaded_type,
)
}
UnionLayout::NullableUnwrapped {
@ -1401,16 +1408,17 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let struct_type = basic_type_from_layout(env, layout_interner, struct_layout);
let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
lookup_at_index_ptr(
env,
layout_interner,
union_layout,
field_layouts,
// the tag id is not stored
*index as usize,
argument.into_pointer_value(),
struct_type.into_struct_type(),
target_loaded_type,
)
}
}
@ -1535,7 +1543,7 @@ fn build_tag_field_value<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
value.into_pointer_value(),
env.context.i64_type().ptr_type(AddressSpace::Generic),
env.context.i64_type().ptr_type(AddressSpace::default()),
"cast_recursive_pointer",
)
.into()
@ -1751,7 +1759,7 @@ fn build_tag<'a, 'ctx, 'env>(
);
if tag_id == *nullable_id as _ {
let output_type = roc_union.struct_type().ptr_type(AddressSpace::Generic);
let output_type = roc_union.struct_type().ptr_type(AddressSpace::default());
return output_type.const_null().into();
}
@ -1993,17 +2001,17 @@ pub fn get_tag_id<'a, 'ctx, 'env>(
fn lookup_at_index_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
union_layout: &UnionLayout<'a>,
field_layouts: &[InLayout<'a>],
index: usize,
value: PointerValue<'ctx>,
struct_type: StructType<'ctx>,
target_loaded_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ptr = env.builder.build_pointer_cast(
value,
struct_type.ptr_type(AddressSpace::Generic),
struct_type.ptr_type(AddressSpace::default()),
"cast_lookup_at_index_ptr",
);
@ -2020,35 +2028,18 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>(
"load_at_index_ptr_old",
);
if let Some(Layout::RecursivePointer(_)) = field_layouts
.get(index as usize)
.map(|l| layout_interner.get(*l))
{
// a recursive field is stored as a `i64*`, to use it we must cast it to
// a pointer to the block of memory representation
let union_layout = layout_interner.insert(Layout::Union(*union_layout));
let actual_type = basic_type_from_layout(env, layout_interner, union_layout);
debug_assert!(actual_type.is_pointer_type());
builder
.build_pointer_cast(
result.into_pointer_value(),
actual_type.into_pointer_type(),
"cast_rec_pointer_lookup_at_index_ptr_old",
)
.into()
} else {
result
}
// A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout
// might want a more precise structure. As such, cast it to the refined type if needed.
cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type)
}
fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
union_layout: &UnionLayout<'a>,
field_layouts: &'a [InLayout<'a>],
index: usize,
value: PointerValue<'ctx>,
target_loaded_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
@ -2058,7 +2049,7 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
let data_ptr = env.builder.build_pointer_cast(
value,
struct_type.ptr_type(AddressSpace::Generic),
struct_type.ptr_type(AddressSpace::default()),
"cast_lookup_at_index_ptr",
);
@ -2080,27 +2071,9 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
"load_at_index_ptr",
);
if let Some(Layout::RecursivePointer(_)) = field_layouts
.get(index as usize)
.map(|l| layout_interner.get(*l))
{
// a recursive field is stored as a `i64*`, to use it we must cast it to
// a pointer to the block of memory representation
let union_layout = layout_interner.insert(Layout::Union(*union_layout));
let actual_type = basic_type_from_layout(env, layout_interner, union_layout);
debug_assert!(actual_type.is_pointer_type());
builder
.build_pointer_cast(
result.into_pointer_value(),
actual_type.into_pointer_type(),
"cast_rec_pointer_lookup_at_index_ptr_new",
)
.into()
} else {
result
}
// A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout
// might want a more precise structure. As such, cast it to the refined type if needed.
cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type)
}
pub fn reserve_with_refcount<'a, 'ctx, 'env>(
@ -2181,7 +2154,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
)
.into_pointer_value();
let ptr_type = value_type.ptr_type(AddressSpace::Generic);
let ptr_type = value_type.ptr_type(AddressSpace::default());
env.builder
.build_pointer_cast(ptr, ptr_type, "alloc_cast_to_desired")
@ -2406,7 +2379,7 @@ pub fn store_roc_value_opaque<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>,
) {
let target_type =
basic_type_from_layout(env, layout_interner, layout).ptr_type(AddressSpace::Generic);
basic_type_from_layout(env, layout_interner, layout).ptr_type(AddressSpace::default());
let destination =
env.builder
.build_pointer_cast(opaque_destination, target_type, "store_roc_value_opaque");
@ -2659,7 +2632,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
let basic_type = basic_type_from_layout(env, layout_interner, param.layout);
let phi_type = if layout_interner.is_passed_by_reference(param.layout) {
basic_type.ptr_type(AddressSpace::Generic).into()
basic_type.ptr_type(AddressSpace::default()).into()
} else {
basic_type
};
@ -3043,6 +3016,24 @@ pub(crate) fn load_symbol_and_layout<'a, 'ctx, 'b>(
}
}
fn equivalent_type_constructors(t1: &BasicTypeEnum, t2: &BasicTypeEnum) -> bool {
use BasicTypeEnum::*;
match (t1, t2) {
(ArrayType(_), ArrayType(_)) => true,
(ArrayType(_), _) => false,
(FloatType(_), FloatType(_)) => true,
(FloatType(_), _) => false,
(IntType(_), IntType(_)) => true,
(IntType(_), _) => false,
(PointerType(_), PointerType(_)) => true,
(PointerType(_), _) => false,
(StructType(_), StructType(_)) => true,
(StructType(_), _) => false,
(VectorType(_), VectorType(_)) => true,
(VectorType(_), _) => false,
}
}
/// Cast a value to another value of the same size, but only if their types are not equivalent.
/// This is needed to allow us to interoperate between recursive pointers in unions that are
/// opaque, and well-typed.
@ -3054,7 +3045,10 @@ pub fn cast_if_necessary_for_opaque_recursive_pointers<'ctx>(
from_value: BasicValueEnum<'ctx>,
to_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
if from_value.get_type() != to_type {
if from_value.get_type() != to_type
// Only perform the cast if the target types are transumatble.
&& equivalent_type_constructors(&from_value.get_type(), &to_type)
{
complex_bitcast(
builder,
from_value,
@ -3202,7 +3196,7 @@ fn complex_bitcast_from_bigger_than_to<'ctx>(
// then read it back as a different type
let to_type_pointer = builder.build_pointer_cast(
argument_pointer,
to_type.ptr_type(inkwell::AddressSpace::Generic),
to_type.ptr_type(inkwell::AddressSpace::default()),
name,
);
@ -3225,7 +3219,7 @@ fn complex_bitcast_to_bigger_than_from<'ctx>(
storage,
from_value
.get_type()
.ptr_type(inkwell::AddressSpace::Generic),
.ptr_type(inkwell::AddressSpace::default()),
name,
);
@ -3586,7 +3580,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
argument_types.insert(0, output_type);
}
Some(return_type) => {
let output_type = return_type.ptr_type(AddressSpace::Generic);
let output_type = return_type.ptr_type(AddressSpace::default());
argument_types.insert(0, output_type.into());
}
}
@ -3638,7 +3632,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
// bitcast the ptr
let fastcc_ptr = env.builder.build_pointer_cast(
arg.into_pointer_value(),
fastcc_type.ptr_type(AddressSpace::Generic),
fastcc_type.ptr_type(AddressSpace::default()),
"bitcast_arg",
);
@ -3733,7 +3727,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let return_type = wrapper_return_type;
let c_function_spec = {
let output_type = return_type.ptr_type(AddressSpace::Generic);
let output_type = return_type.ptr_type(AddressSpace::default());
argument_types.push(output_type.into());
FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types)
};
@ -3842,6 +3836,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func(
@ -3897,7 +3892,10 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
let c_abi_roc_str_type = env.context.struct_type(
&[
env.context.i8_type().ptr_type(AddressSpace::Generic).into(),
env.context
.i8_type()
.ptr_type(AddressSpace::default())
.into(),
env.ptr_int().into(),
env.ptr_int().into(),
],
@ -3955,20 +3953,40 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
(RocReturn::ByPointer, CCReturn::Return) => {
// Roc currently puts the return pointer at the end of the argument list.
// As such, we drop the last element here instead of the first.
(&params[..], &param_types[..param_types.len() - 1])
(
&params[..],
&param_types[..param_types.len().saturating_sub(1)],
)
}
// Drop the return pointer the other way, if the C function returns by pointer but Roc
// doesn't
(RocReturn::Return, CCReturn::ByPointer) => (&params[1..], &param_types[..]),
(RocReturn::ByPointer, CCReturn::ByPointer) => {
// Both return by pointer but Roc puts it at the end and C puts it at the beginning
(&params[1..], &param_types[..param_types.len() - 1])
(
&params[1..],
&param_types[..param_types.len().saturating_sub(1)],
)
}
(RocReturn::Return, CCReturn::Void) => {
// the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`.
// In C, this is modelled as a function returning void
(&params[..], &param_types[..])
}
(RocReturn::ByPointer, CCReturn::Void) => {
// the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`.
// In C, this is modelled as a function returning void
(
&params[..],
&param_types[..param_types.len().saturating_sub(1)],
)
}
_ => (&params[..], &param_types[..]),
};
debug_assert!(
params.len() == param_types.len(),
debug_assert_eq!(
params.len(),
param_types.len(),
"when exposing a function to the host, params.len() was {}, but param_types.len() was {}",
params.len(),
param_types.len()
@ -4016,7 +4034,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
// bitcast the ptr
let fastcc_ptr = env.builder.build_pointer_cast(
arg.into_pointer_value(),
fastcc_type.ptr_type(AddressSpace::Generic),
fastcc_type.ptr_type(AddressSpace::default()),
"bitcast_arg",
);
@ -4102,7 +4120,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
)
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {}
}
// a generic version that writes the result into a passed *u8 pointer
@ -4131,7 +4149,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function_name: String = format!("{}_size", c_function_name);
let size_function = add_func(
env.context,
@ -4155,7 +4173,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {
basic_type_from_layout(env, layout_interner, return_layout)
}
};
@ -4193,7 +4211,7 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
env.builder.build_pointer_cast(
global.as_pointer_value(),
env.context.i32_type().ptr_type(AddressSpace::Generic),
env.context.i32_type().ptr_type(AddressSpace::default()),
"cast_sjlj_buffer",
)
}
@ -4210,12 +4228,12 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
let buf_type = env
.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::default())
.array_type(5);
let jmp_buf_i8p_arr = env.builder.build_pointer_cast(
jmp_buf,
buf_type.ptr_type(AddressSpace::Generic),
buf_type.ptr_type(AddressSpace::default()),
"jmp_buf [5 x i8*]",
);
@ -4256,7 +4274,7 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
.builder
.build_pointer_cast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"jmp_buf i8*",
)
.into();
@ -4413,7 +4431,7 @@ fn roc_call_result_type<'a, 'ctx, 'env>(
env.context.struct_type(
&[
env.context.i64_type().into(),
zig_str_type(env).ptr_type(AddressSpace::Generic).into(),
zig_str_type(env).ptr_type(AddressSpace::default()).into(),
return_type,
],
false,
@ -4489,7 +4507,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
basic_type_from_layout(env, layout_interner, return_layout),
);
// argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into());
// argument_types.push(wrapper_return_type.ptr_type(AddressSpace::default()).into());
// let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false);
let wrapper_function_spec = FunctionSpec::cconv(
@ -4591,8 +4609,9 @@ pub fn build_procedures<'a, 'ctx, 'env>(
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
debug_output_file: Option<&Path>,
glue_layouts: &GlueLayouts<'a>,
) {
build_procedures_help(
let mod_solutions = build_procedures_help(
env,
layout_interner,
opt_level,
@ -4600,6 +4619,43 @@ pub fn build_procedures<'a, 'ctx, 'env>(
entry_point,
debug_output_file,
);
let niche = Niche::NONE;
for (symbol, top_level) in glue_layouts.getters.iter().copied() {
let it = top_level.arguments.iter().copied();
let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, niche, top_level.result);
let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs();
let Some(func_spec) = it.next() else {
// TODO this means a function was not considered host-exposed in mono
continue;
};
debug_assert!(
it.next().is_none(),
"we expect only one specialization of this symbol"
);
// NOTE fake layout; it is only used for debug prints
let getter_fn =
function_value_by_func_spec(env, *func_spec, symbol, &[], niche, Layout::UNIT);
let name = getter_fn.get_name().to_str().unwrap();
let getter_name = symbol.as_str(&env.interns);
// Add the getter function to the module.
let _ = expose_function_to_host_help_c_abi(
env,
layout_interner,
name,
getter_fn,
top_level.arguments,
top_level.result,
getter_name,
);
}
}
pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
@ -4919,28 +4975,23 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
mod_solutions: &'a ModSolutions,
proc_name: LambdaName,
fn_name: &str,
alias_symbol: Symbol,
exposed_function_symbol: Symbol,
top_level: ProcLayout<'a>,
layout: RawFunctionLayout<'a>,
hels: &HostExposedLambdaSet<'a>,
) {
let ident_string = proc_name.name().as_str(&env.interns);
let fn_name: String = format!("{}_1", ident_string);
match layout {
match hels.raw_function_layout {
RawFunctionLayout::Function(arguments, closure, result) => {
// define closure size and return value size, e.g.
//
// * roc__mainForHost_1_Update_size() -> i64
// * roc__mainForHost_1_Update_result_size() -> i64
let it = top_level.arguments.iter().copied();
let it = hels.proc_layout.arguments.iter().copied();
let bytes = roc_alias_analysis::func_name_bytes_help(
exposed_function_symbol,
hels.symbol,
it,
Niche::NONE,
top_level.result,
hels.proc_layout.result,
);
let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
@ -4956,24 +5007,24 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
function_value_by_func_spec(
env,
*func_spec,
exposed_function_symbol,
top_level.arguments,
hels.symbol,
hels.proc_layout.arguments,
Niche::NONE,
top_level.result,
hels.proc_layout.result,
)
}
None => {
// morphic did not generate a specialization for this function,
// therefore it must actually be unused.
// An example is our closure callers
panic!("morphic did not specialize {:?}", exposed_function_symbol);
panic!("morphic did not specialize {:?}", hels.symbol);
}
};
build_closure_caller(
env,
layout_interner,
&fn_name,
fn_name,
evaluator,
alias_symbol,
arguments,
@ -4992,7 +5043,7 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
build_host_exposed_alias_size_help(
env,
&fn_name,
fn_name,
alias_symbol,
Some("result"),
result_type,
@ -5016,7 +5067,7 @@ fn build_closure_caller<'a, 'ctx, 'env>(
for layout in arguments {
let arg_type = basic_type_from_layout(env, layout_interner, *layout);
let arg_ptr_type = arg_type.ptr_type(AddressSpace::Generic);
let arg_ptr_type = arg_type.ptr_type(AddressSpace::default());
argument_types.push(arg_ptr_type.into());
}
@ -5025,7 +5076,7 @@ fn build_closure_caller<'a, 'ctx, 'env>(
let basic_type =
basic_type_from_layout(env, layout_interner, lambda_set.runtime_representation());
basic_type.ptr_type(AddressSpace::Generic)
basic_type.ptr_type(AddressSpace::default())
};
argument_types.push(closure_argument_type.into());
@ -5034,18 +5085,13 @@ fn build_closure_caller<'a, 'ctx, 'env>(
let result_type = basic_type_from_layout(env, layout_interner, result);
let output_type = { result_type.ptr_type(AddressSpace::Generic) };
let output_type = { result_type.ptr_type(AddressSpace::default()) };
argument_types.push(output_type.into());
// STEP 1: build function header
// e.g. `roc__main_1_Fx_caller`
let function_name = format!(
"roc__{}_{}_{}_caller",
def_name,
alias_symbol.module_string(&env.interns),
alias_symbol.as_str(&env.interns)
);
// e.g. `roc__mainForHost_0_caller` (def_name is `mainForHost_0`)
let function_name = format!("roc__{}_caller", def_name);
let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types);
@ -5156,7 +5202,7 @@ fn build_host_exposed_alias_size<'a, 'r, 'ctx, 'env>(
fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>,
def_name: &str,
alias_symbol: Symbol,
_alias_symbol: Symbol,
opt_label: Option<&str>,
basic_type: BasicTypeEnum<'ctx>,
) {
@ -5166,20 +5212,9 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
let i64 = env.context.i64_type().as_basic_type_enum();
let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]);
let size_function_name: String = if let Some(label) = opt_label {
format!(
"roc__{}_{}_{}_{}_size",
def_name,
alias_symbol.module_string(&env.interns),
alias_symbol.as_str(&env.interns),
label
)
format!("roc__{}_{}_size", def_name, label)
} else {
format!(
"roc__{}_{}_{}_size",
def_name,
alias_symbol.module_string(&env.interns),
alias_symbol.as_str(&env.interns)
)
format!("roc__{}_size", def_name,)
};
let size_function = add_func(
@ -5198,7 +5233,7 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
builder.build_return(Some(&size));
}
pub fn build_proc<'a, 'ctx, 'env>(
fn build_proc<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
mod_solutions: &'a ModSolutions,
@ -5219,17 +5254,18 @@ pub fn build_proc<'a, 'ctx, 'env>(
GenTest | WasmGenTest | CliTest => {
/* no host, or exposing types is not supported */
}
Binary | BinaryDev => {
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
Binary | BinaryDev | BinaryGlue => {
for (alias_name, hels) in aliases.iter() {
let ident_string = proc.name.name().as_str(&env.interns);
let fn_name: String = format!("{}_{}", ident_string, hels.id.0);
expose_alias_to_host(
env,
layout_interner,
mod_solutions,
proc.name,
&fn_name,
*alias_name,
*generated_function,
*top_level,
*layout,
hels,
)
}
}
@ -5550,7 +5586,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
basic_type_from_builtin(env, builtin)
}
Builtin::Str | Builtin::List(_) => {
let address_space = AddressSpace::Generic;
let address_space = AddressSpace::default();
let field_types: [BasicTypeEnum; 3] = [
env.context.i8_type().ptr_type(address_space).into(),
env.ptr_int().into(),
@ -5665,7 +5701,7 @@ impl<'ctx> FunctionSpec<'ctx> {
let (typ, opt_sret_parameter) = match cc_return {
CCReturn::ByPointer => {
// turn the output type into a pointer type. Make it the first argument to the function
let output_type = return_type.unwrap().ptr_type(AddressSpace::Generic);
let output_type = return_type.unwrap().ptr_type(AddressSpace::default());
let mut arguments: Vec<'_, BasicTypeEnum> =
bumpalo::vec![in env.arena; output_type.into()];
@ -5683,6 +5719,8 @@ impl<'ctx> FunctionSpec<'ctx> {
(return_type.unwrap().fn_type(&arguments, false), None)
}
CCReturn::Void => {
// NOTE: there may be a valid return type, but it is zero-sized.
// for instance just `{}` or something more complex like `{ { {}, {} }, {} }`
let arguments = function_arguments(env, argument_types);
(env.context.void_type().fn_type(&arguments, false), None)
}
@ -5707,7 +5745,7 @@ impl<'ctx> FunctionSpec<'ctx> {
return_type.fn_type(&function_arguments(env, &argument_types), false)
}
RocReturn::ByPointer => {
argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
argument_types.push(return_type.ptr_type(AddressSpace::default()).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
@ -5955,7 +5993,7 @@ fn define_global_str_literal_ptr<'a, 'ctx, 'env>(
let ptr = env.builder.build_pointer_cast(
global.as_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"to_opaque",
);
@ -6090,7 +6128,7 @@ pub fn add_func<'ctx>(
) -> FunctionValue<'ctx> {
if cfg!(debug_assertions) {
if let Some(func) = module.get_function(name) {
panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:?}", name, func);
panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:#?}", name, func);
}
}
@ -6100,23 +6138,3 @@ pub fn add_func<'ctx>(
fn_val
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),
}
impl<'a> WhenRecursive<'a> {
pub fn unwrap_recursive_pointer(&self, layout: Layout<'a>) -> Layout<'a> {
match layout {
Layout::RecursivePointer(_) => match self {
WhenRecursive::Loop(lay) => Layout::Union(*lay),
WhenRecursive::Unreachable => {
internal_error!("cannot compare recursive pointers outside of a structure")
}
},
_ => layout,
}
}
}

View file

@ -74,7 +74,7 @@ fn pass_element_as_opaque<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
element_ptr,
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"pass_element_as_opaque",
)
.into()
@ -97,7 +97,7 @@ pub(crate) fn pass_as_opaque<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
ptr,
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"pass_as_opaque",
)
.into()
@ -133,7 +133,7 @@ pub(crate) fn list_get_unsafe<'a, 'ctx, 'env>(
let builder = env.builder;
let elem_type = basic_type_from_layout(env, layout_interner, element_layout);
let ptr_type = elem_type.ptr_type(AddressSpace::Generic);
let ptr_type = elem_type.ptr_type(AddressSpace::default());
// Load the pointer to the array data
let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
@ -183,6 +183,26 @@ pub(crate) fn list_reserve<'a, 'ctx, 'env>(
)
}
/// List.releaseExcessCapacity : List elem -> List elem
pub(crate) fn list_release_excess_capacity<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
list: BasicValueEnum<'ctx>,
element_layout: InLayout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
call_list_bitcode_fn_1(
env,
list.into_struct_value(),
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
pass_update_mode(env, update_mode),
],
bitcode::LIST_RELEASE_EXCESS_CAPACITY,
)
}
/// List.appendUnsafe : List elem, elem -> List elem
pub(crate) fn list_append_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -393,17 +413,36 @@ pub(crate) fn list_len<'ctx>(
.into_int_value()
}
/// List.capacity : List * -> Nat
pub(crate) fn list_capacity<'ctx>(
pub(crate) fn list_capacity_or_ref_ptr<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "list_capacity")
.build_extract_value(
wrapper_struct,
Builtin::WRAPPER_CAPACITY,
"list_capacity_or_ref_ptr",
)
.unwrap()
.into_int_value()
}
// Gets a pointer to just after the refcount for a list or seamless slice.
// The value is just after the refcount so that normal lists and seamless slices can share code paths easily.
pub(crate) fn list_refcount_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
wrapper_struct: StructValue<'ctx>,
) -> PointerValue<'ctx> {
call_list_bitcode_fn(
env,
&[wrapper_struct],
&[],
BitcodeReturns::Basic,
bitcode::LIST_REFCOUNT_PTR,
)
.into_pointer_value()
}
pub(crate) fn destructure<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
@ -801,11 +840,7 @@ pub(crate) fn decref<'a, 'ctx, 'env>(
wrapper_struct: StructValue<'ctx>,
alignment: u32,
) {
let (_, pointer) = load_list(
env.builder,
wrapper_struct,
env.context.i8_type().ptr_type(AddressSpace::Generic),
);
let refcount_ptr = list_refcount_ptr(env, wrapper_struct);
crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
crate::llvm::refcounting::decref_pointer_check_null(env, refcount_ptr, alignment);
}

View file

@ -32,7 +32,7 @@ pub(crate) fn decode_from_utf8_result<'a, 'ctx, 'env>(
PtrWidth::Bytes4 | PtrWidth::Bytes8 => {
let result_ptr_cast = env.builder.build_pointer_cast(
pointer,
record_type.ptr_type(AddressSpace::Generic),
record_type.ptr_type(AddressSpace::default()),
"to_unnamed",
);
@ -63,3 +63,19 @@ pub(crate) fn str_equal<'a, 'ctx, 'env>(
bitcode::STR_EQUAL,
)
}
// Gets a pointer to just after the refcount for a list or seamless slice.
// The value is just after the refcount so that normal lists and seamless slices can share code paths easily.
pub(crate) fn str_refcount_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: BasicValueEnum<'ctx>,
) -> PointerValue<'ctx> {
call_str_bitcode_fn(
env,
&[value],
&[],
BitcodeReturns::Basic,
bitcode::STR_REFCOUNT_PTR,
)
.into_pointer_value()
}

View file

@ -1,17 +1,14 @@
use crate::llvm::build::{
get_tag_id, tag_pointer_clear_tag_id, Env, WhenRecursive, FAST_CALL_CONV,
};
use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV};
use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;
use inkwell::types::BasicType;
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_builtins::bitcode;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner, UnionLayout,
@ -38,7 +35,6 @@ pub fn generic_eq<'a, 'ctx, 'env>(
rhs_val,
lhs_layout,
rhs_layout,
WhenRecursive::Unreachable,
)
}
@ -59,7 +55,6 @@ pub fn generic_neq<'a, 'ctx, 'env>(
rhs_val,
lhs_layout,
rhs_layout,
WhenRecursive::Unreachable,
)
}
@ -69,8 +64,8 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
builtin_layout: InLayout<'a>,
builtin: &Builtin<'a>,
when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let int_cmp = |pred, label| {
let int_val = env.builder.build_int_compare(
@ -129,19 +124,15 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::Decimal => dec_binop_with_unchecked(env, bitcode::DEC_EQ, lhs_val, rhs_val),
Builtin::Str => str_equal(env, lhs_val, rhs_val),
Builtin::List(elem) => {
let list_layout = layout_interner.insert(Layout::Builtin(*builtin));
build_list_eq(
env,
layout_interner,
layout_ids,
list_layout,
*elem,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
when_recursive,
)
}
Builtin::List(elem) => build_list_eq(
env,
layout_interner,
layout_ids,
builtin_layout,
*elem,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
),
}
}
@ -153,7 +144,6 @@ fn build_eq<'a, 'ctx, 'env>(
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: InLayout<'a>,
rhs_layout: InLayout<'a>,
when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let lhs_layout = &layout_interner.runtime_representation_in(lhs_layout);
let rhs_layout = &layout_interner.runtime_representation_in(rhs_layout);
@ -171,16 +161,16 @@ fn build_eq<'a, 'ctx, 'env>(
layout_ids,
lhs_val,
rhs_val,
*lhs_layout,
&builtin,
when_recursive,
),
Layout::Struct { field_layouts, .. } => build_struct_eq(
env,
layout_interner,
layout_ids,
*lhs_layout,
field_layouts,
when_recursive,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
),
@ -191,7 +181,7 @@ fn build_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
*lhs_layout,
&union_layout,
lhs_val,
rhs_val,
@ -201,47 +191,48 @@ fn build_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
*lhs_layout,
inner_layout,
lhs_val,
rhs_val,
),
Layout::RecursivePointer(_) => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be compared directly")
}
Layout::RecursivePointer(rec_layout) => {
let layout = rec_layout;
WhenRecursive::Loop(union_layout) => {
let layout = layout_interner.insert(Layout::Union(union_layout));
let bt = basic_type_from_layout(env, layout_interner, layout);
let bt = basic_type_from_layout(env, layout_interner, layout);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env.builder.build_pointer_cast(
lhs_val.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env.builder.build_pointer_cast(
lhs_val.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
let field2_cast = env.builder.build_pointer_cast(
rhs_val.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
let field2_cast = env.builder.build_pointer_cast(
rhs_val.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
let union_layout = match layout_interner.get(rec_layout) {
Layout::Union(union_layout) => {
debug_assert!(!matches!(union_layout, UnionLayout::NonRecursive(..)));
union_layout
}
_ => internal_error!(),
};
build_tag_eq(
env,
layout_interner,
layout_ids,
WhenRecursive::Loop(union_layout),
&union_layout,
field1_cast.into(),
field2_cast.into(),
)
}
},
build_tag_eq(
env,
layout_interner,
layout_ids,
rec_layout,
&union_layout,
field1_cast.into(),
field2_cast.into(),
)
}
}
}
@ -251,8 +242,8 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
builtin_layout: InLayout<'a>,
builtin: &Builtin<'a>,
when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let int_cmp = |pred, label| {
let int_val = env.builder.build_int_compare(
@ -317,7 +308,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
result.into()
}
Builtin::List(elem) => {
let builtin_layout = layout_interner.insert(Layout::Builtin(*builtin));
let is_equal = build_list_eq(
env,
layout_interner,
@ -326,7 +316,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
*elem,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
when_recursive,
)
.into_int_value();
@ -345,7 +334,6 @@ fn build_neq<'a, 'ctx, 'env>(
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: InLayout<'a>,
rhs_layout: InLayout<'a>,
when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
if lhs_layout != rhs_layout {
panic!(
@ -361,8 +349,8 @@ fn build_neq<'a, 'ctx, 'env>(
layout_ids,
lhs_val,
rhs_val,
lhs_layout,
&builtin,
when_recursive,
),
Layout::Struct { field_layouts, .. } => {
@ -370,8 +358,8 @@ fn build_neq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
lhs_layout,
field_layouts,
when_recursive,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
)
@ -387,7 +375,7 @@ fn build_neq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
lhs_layout,
&union_layout,
lhs_val,
rhs_val,
@ -404,7 +392,6 @@ fn build_neq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
lhs_layout,
inner_layout,
lhs_val,
@ -432,15 +419,17 @@ fn build_list_eq<'a, 'ctx, 'env>(
element_layout: InLayout<'a>,
list1: StructValue<'ctx>,
list2: StructValue<'ctx>,
when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::LIST_EQ;
let element_layout = layout_interner.get(element_layout);
let element_layout = when_recursive.unwrap_recursive_pointer(element_layout);
let element_layout = layout_interner.insert(element_layout);
let element_layout = if let Layout::RecursivePointer(rec) = layout_interner.get(element_layout)
{
rec
} else {
element_layout
};
let fn_name = layout_ids
.get(symbol, &element_layout)
.to_symbol_string(symbol, &env.interns);
@ -461,7 +450,6 @@ fn build_list_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
function_value,
element_layout,
);
@ -471,8 +459,7 @@ fn build_list_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[list1.into(), list2.into()], "list_eq");
@ -486,7 +473,6 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
element_layout: InLayout<'a>,
) {
@ -511,7 +497,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(ctx, loc);
builder.set_current_debug_location(loc);
}
// Add args to scope
@ -548,7 +534,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
let builder = env.builder;
let element_type = basic_type_from_layout(env, layout_interner, element_layout);
let ptr_type = element_type.ptr_type(AddressSpace::Generic);
let ptr_type = element_type.ptr_type(AddressSpace::default());
let ptr1 = load_list_ptr(env.builder, list1, ptr_type);
let ptr2 = load_list_ptr(env.builder, list2, ptr_type);
@ -605,7 +591,6 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
elem2,
element_layout,
element_layout,
when_recursive,
)
.into_int_value();
@ -646,16 +631,14 @@ fn build_struct_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
struct_layout: InLayout<'a>,
field_layouts: &'a [InLayout<'a>],
when_recursive: WhenRecursive<'a>,
struct1: StructValue<'ctx>,
struct2: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let symbol = Symbol::GENERIC_EQ;
let fn_name = layout_ids
.get(symbol, &struct_layout)
@ -678,7 +661,6 @@ fn build_struct_eq<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
function_value,
when_recursive,
field_layouts,
);
@ -687,8 +669,7 @@ fn build_struct_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[struct1.into(), struct2.into()], "struct_eq");
@ -703,7 +684,6 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
when_recursive: WhenRecursive<'a>,
field_layouts: &[InLayout<'a>],
) {
let ctx = env.context;
@ -727,7 +707,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(ctx, loc);
builder.set_current_debug_location(loc);
}
// Add args to scope
@ -761,42 +741,40 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
.build_extract_value(struct2, index as u32, "eq_field")
.unwrap();
let are_equal = if let Layout::RecursivePointer(_) = layout_interner.get(*field_layout) {
match &when_recursive {
WhenRecursive::Unreachable => {
unreachable!("The current layout should not be recursive, but is")
}
WhenRecursive::Loop(union_layout) => {
let field_layout = layout_interner.insert(Layout::Union(*union_layout));
let are_equal = if let Layout::RecursivePointer(rec_layout) =
layout_interner.get(*field_layout)
{
debug_assert!(
matches!(layout_interner.get(rec_layout), Layout::Union(union_layout) if !matches!(union_layout, UnionLayout::NonRecursive(..)))
);
let bt = basic_type_from_layout(env, layout_interner, field_layout);
let field_layout = rec_layout;
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env.builder.build_pointer_cast(
field1.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
let bt = basic_type_from_layout(env, layout_interner, field_layout);
let field2_cast = env.builder.build_pointer_cast(
field2.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env.builder.build_pointer_cast(
field1.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
build_eq(
env,
layout_interner,
layout_ids,
field1_cast.into(),
field2_cast.into(),
field_layout,
field_layout,
WhenRecursive::Loop(*union_layout),
)
.into_int_value()
}
}
let field2_cast = env.builder.build_pointer_cast(
field2.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
build_eq(
env,
layout_interner,
layout_ids,
field1_cast.into(),
field2_cast.into(),
field_layout,
field_layout,
)
.into_int_value()
} else {
let lhs = use_roc_value(env, layout_interner, *field_layout, field1, "field1");
let rhs = use_roc_value(env, layout_interner, *field_layout, field2, "field2");
@ -808,7 +786,6 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
rhs,
*field_layout,
*field_layout,
when_recursive,
)
.into_int_value()
};
@ -839,7 +816,7 @@ fn build_tag_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: WhenRecursive<'a>,
tag_layout: InLayout<'a>,
union_layout: &UnionLayout<'a>,
tag1: BasicValueEnum<'ctx>,
tag2: BasicValueEnum<'ctx>,
@ -847,7 +824,6 @@ fn build_tag_eq<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let tag_layout = layout_interner.insert(Layout::Union(*union_layout));
let symbol = Symbol::GENERIC_EQ;
let fn_name = layout_ids
.get(symbol, &tag_layout)
@ -869,7 +845,6 @@ fn build_tag_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
function_value,
union_layout,
);
@ -879,8 +854,7 @@ fn build_tag_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[tag1.into(), tag2.into()], "tag_eq");
@ -894,7 +868,6 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
union_layout: &UnionLayout<'a>,
) {
@ -919,7 +892,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(ctx, loc);
builder.set_current_debug_location(loc);
}
// Add args to scope
@ -999,12 +972,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let struct_layout =
layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
union_layout,
Some(when_recursive),
struct_layout,
field_layouts,
tag1,
tag2,
@ -1070,12 +1045,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let struct_layout =
layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
union_layout,
None,
struct_layout,
field_layouts,
tag1,
tag2,
@ -1131,12 +1108,13 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.position_at_end(compare_other);
let struct_layout = layout_interner.insert(Layout::struct_no_name_order(other_fields));
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
union_layout,
None,
struct_layout,
other_fields,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
@ -1229,12 +1207,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let struct_layout =
layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
union_layout,
None,
struct_layout,
field_layouts,
tag1,
tag2,
@ -1268,12 +1248,13 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.position_at_end(compare_fields);
let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
union_layout,
None,
struct_layout,
field_layouts,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
@ -1288,27 +1269,24 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
union_layout: &UnionLayout<'a>,
opt_when_recursive: Option<WhenRecursive<'a>>,
struct_layout: InLayout<'a>,
field_layouts: &'a [InLayout<'a>],
tag1: PointerValue<'ctx>,
tag2: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let wrapper_type = basic_type_from_layout(env, layout_interner, struct_layout);
debug_assert!(wrapper_type.is_struct_type());
// cast the opaque pointer to a pointer of the correct shape
let struct1_ptr = env.builder.build_pointer_cast(
tag1,
wrapper_type.ptr_type(AddressSpace::Generic),
wrapper_type.ptr_type(AddressSpace::default()),
"opaque_to_correct",
);
let struct2_ptr = env.builder.build_pointer_cast(
tag2,
wrapper_type.ptr_type(AddressSpace::Generic),
wrapper_type.ptr_type(AddressSpace::default()),
"opaque_to_correct",
);
@ -1326,8 +1304,8 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
struct_layout,
field_layouts,
opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)),
struct1,
struct2,
)
@ -1340,7 +1318,6 @@ fn build_box_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: WhenRecursive<'a>,
box_layout: InLayout<'a>,
inner_layout: InLayout<'a>,
tag1: BasicValueEnum<'ctx>,
@ -1370,7 +1347,6 @@ fn build_box_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
function_value,
inner_layout,
);
@ -1380,8 +1356,7 @@ fn build_box_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[tag1.into(), tag2.into()], "tag_eq");
@ -1395,7 +1370,6 @@ fn build_box_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
inner_layout: InLayout<'a>,
) {
@ -1420,7 +1394,7 @@ fn build_box_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(ctx, loc);
builder.set_current_debug_location(loc);
}
// Add args to scope
@ -1472,7 +1446,6 @@ fn build_box_eq_help<'a, 'ctx, 'env>(
value2,
inner_layout,
inner_layout,
when_recursive,
);
env.builder.build_return(Some(&is_equal));

View file

@ -18,7 +18,9 @@ fn basic_type_from_record<'a, 'ctx, 'env>(
let mut field_types = Vec::with_capacity_in(fields.len(), env.arena);
for field_layout in fields.iter() {
field_types.push(basic_type_from_layout(env, layout_interner, *field_layout));
let typ = basic_type_from_layout(env, layout_interner, *field_layout);
field_types.push(typ);
}
env.context
@ -44,13 +46,13 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
Boxed(inner_layout) => {
let inner_type = basic_type_from_layout(env, layout_interner, inner_layout);
inner_type.ptr_type(AddressSpace::Generic).into()
inner_type.ptr_type(AddressSpace::default()).into()
}
Union(union_layout) => basic_type_from_union_layout(env, layout_interner, &union_layout),
RecursivePointer(_) => env
.context
.i64_type()
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::default())
.as_basic_type_enum(),
Builtin(builtin) => basic_type_from_builtin(env, &builtin),
@ -65,6 +67,7 @@ pub fn struct_type_from_union_layout<'a, 'ctx, 'env>(
use UnionLayout::*;
match union_layout {
NonRecursive([]) => env.context.struct_type(&[], false),
NonRecursive(tags) => {
RocUnion::tagged_from_slices(layout_interner, env.context, tags, env.target_info)
.struct_type()
@ -109,7 +112,7 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
Recursive(_)
| NonNullableUnwrapped(_)
| NullableWrapped { .. }
| NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::Generic).into(),
| NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::default()).into(),
}
}
@ -158,7 +161,7 @@ pub fn argument_type_from_layout<'a, 'ctx, 'env>(
let base = basic_type_from_layout(env, layout_interner, layout);
if layout_interner.is_passed_by_reference(layout) {
base.ptr_type(AddressSpace::Generic).into()
base.ptr_type(AddressSpace::default()).into()
} else {
base
}
@ -176,7 +179,7 @@ pub fn argument_type_from_union_layout<'a, 'ctx, 'env>(
let heap_type = basic_type_from_union_layout(env, layout_interner, union_layout);
if let UnionLayout::NonRecursive(_) = union_layout {
heap_type.ptr_type(AddressSpace::Generic).into()
heap_type.ptr_type(AddressSpace::default()).into()
} else {
heap_type
}
@ -300,7 +303,8 @@ impl<'ctx> RocUnion<'ctx> {
target_info: TargetInfo,
) -> Self {
let tag_type = match layouts.len() {
0..=255 => TagType::I8,
0 => unreachable!("zero-element tag union is not represented as a RocUnion"),
1..=255 => TagType::I8,
_ => TagType::I16,
};
@ -322,6 +326,10 @@ impl<'ctx> RocUnion<'ctx> {
Self::new(context, target_info, data_align, data_width, None)
}
pub fn data_width(&self) -> u32 {
self.data_width
}
pub fn tag_alignment(&self) -> u32 {
let tag_id_alignment = match self.tag_type {
None => 0,
@ -375,7 +383,7 @@ impl<'ctx> RocUnion<'ctx> {
let cast_pointer = env.builder.build_pointer_cast(
data_buffer,
data.get_type().ptr_type(AddressSpace::Generic),
data.get_type().ptr_type(AddressSpace::default()),
"to_data_ptr",
);
@ -436,7 +444,7 @@ pub fn zig_dec_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ct
}
pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::Generic);
let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::default());
env.context
.struct_type(&[env.context.bool_type().into(), u8_ptr_t.into()], false)

View file

@ -9,6 +9,7 @@ use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::ir::LookupType;
use roc_mono::layout::{
@ -19,7 +20,7 @@ use roc_region::all::Region;
use super::build::BuilderExt;
use super::build::{
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, LlvmBackendMode,
Scope, WhenRecursive,
Scope,
};
use super::convert::struct_type_from_union_layout;
@ -98,7 +99,7 @@ fn read_state<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
ptr: PointerValue<'ctx>,
) -> (IntValue<'ctx>, IntValue<'ctx>) {
let ptr_type = env.ptr_int().ptr_type(AddressSpace::Generic);
let ptr_type = env.ptr_int().ptr_type(AddressSpace::default());
let ptr = env.builder.build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
@ -118,7 +119,7 @@ fn write_state<'a, 'ctx, 'env>(
count: IntValue<'ctx>,
offset: IntValue<'ctx>,
) {
let ptr_type = env.ptr_int().ptr_type(AddressSpace::Generic);
let ptr_type = env.ptr_int().ptr_type(AddressSpace::default());
let ptr = env.builder.build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
@ -128,6 +129,15 @@ fn write_state<'a, 'ctx, 'env>(
env.builder.build_store(offset_ptr, offset);
}
fn offset_add<'ctx>(
builder: &Builder<'ctx>,
current: IntValue<'ctx>,
extra: u32,
) -> IntValue<'ctx> {
let intval = current.get_type().const_int(extra as _, false);
builder.build_int_add(current, intval, "offset_add")
}
pub(crate) fn notify_parent_expect(env: &Env, shared_memory: &SharedMemoryPointer) {
let func = env
.module
@ -220,7 +230,6 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
cursors,
value,
layout,
WhenRecursive::Unreachable,
);
offset = extra_offset;
@ -252,7 +261,7 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
)
};
let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::Generic);
let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::default());
let ptr = env
.builder
.build_pointer_cast(ptr, u32_ptr, "cast_ptr_type");
@ -286,7 +295,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
layout: InLayout<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
match layout_interner.get(layout) {
Layout::Builtin(builtin) => build_clone_builtin(
@ -297,7 +305,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
builtin,
when_recursive,
),
Layout::Struct { field_layouts, .. } => build_clone_struct(
@ -308,7 +315,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
field_layouts,
when_recursive,
),
// Since we will never actually display functions (and hence lambda sets)
@ -326,7 +332,7 @@ fn build_clone<'a, 'ctx, 'env>(
)
};
let ptr_type = value.get_type().ptr_type(AddressSpace::Generic);
let ptr_type = value.get_type().ptr_type(AddressSpace::default());
let ptr = env
.builder
.build_pointer_cast(ptr, ptr_type, "cast_ptr_type");
@ -343,7 +349,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
union_layout,
WhenRecursive::Loop(union_layout),
)
}
}
@ -376,39 +381,39 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
inner_layout,
when_recursive,
)
}
Layout::RecursivePointer(_) => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be compared directly")
}
Layout::RecursivePointer(rec_layout) => {
let layout = rec_layout;
WhenRecursive::Loop(union_layout) => {
let layout = layout_interner.insert(Layout::Union(union_layout));
let bt = basic_type_from_layout(env, layout_interner, layout);
let bt = basic_type_from_layout(env, layout_interner, layout);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env.builder.build_pointer_cast(
value.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env.builder.build_pointer_cast(
value.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
let union_layout = match layout_interner.get(rec_layout) {
Layout::Union(union_layout) => {
debug_assert!(!matches!(union_layout, UnionLayout::NonRecursive(..)));
union_layout
}
_ => internal_error!(),
};
build_clone_tag(
env,
layout_interner,
layout_ids,
ptr,
cursors,
field1_cast.into(),
union_layout,
WhenRecursive::Loop(union_layout),
)
}
},
build_clone_tag(
env,
layout_interner,
layout_ids,
ptr,
cursors,
field1_cast.into(),
union_layout,
)
}
}
}
@ -420,7 +425,6 @@ fn build_clone_struct<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
field_layouts: &[InLayout<'a>],
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
let layout = Layout::struct_no_name_order(field_layouts);
@ -447,7 +451,6 @@ fn build_clone_struct<'a, 'ctx, 'env>(
cursors,
field,
*field_layout,
when_recursive,
);
let field_width = env
@ -472,7 +475,6 @@ fn build_clone_tag<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
union_layout: UnionLayout<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
let layout = layout_interner.insert(Layout::Union(union_layout));
let layout_id = layout_ids.get(Symbol::CLONE, &layout);
@ -486,7 +488,10 @@ fn build_clone_tag<'a, 'ctx, 'env>(
let function_type = env.ptr_int().fn_type(
&[
env.context.i8_type().ptr_type(AddressSpace::Generic).into(),
env.context
.i8_type()
.ptr_type(AddressSpace::default())
.into(),
env.ptr_int().into(),
env.ptr_int().into(),
BasicMetadataTypeEnum::from(value.get_type()),
@ -512,13 +517,11 @@ fn build_clone_tag<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
union_layout,
when_recursive,
function_value,
);
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function_value
}
@ -563,19 +566,68 @@ fn load_tag_data<'a, 'ctx, 'env>(
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
tag_type.ptr_type(AddressSpace::Generic),
tag_type.ptr_type(AddressSpace::default()),
"data_ptr",
);
env.builder.new_build_load(tag_type, data_ptr, "load_data")
}
fn clone_tag_payload_and_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
ptr: PointerValue<'ctx>,
cursors: Cursors<'ctx>,
roc_union: RocUnion<'ctx>,
tag_id: usize,
payload_in_layout: InLayout<'a>,
opaque_payload_ptr: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let payload_type = basic_type_from_layout(env, layout_interner, payload_in_layout);
let payload_ptr = env.builder.build_pointer_cast(
opaque_payload_ptr,
payload_type.ptr_type(AddressSpace::default()),
"cast_payload_ptr",
);
let payload = env
.builder
.new_build_load(payload_type, payload_ptr, "payload");
// NOTE: `answer` includes any extra_offset that the tag payload may have needed
// (e.g. because it includes a list). That is what we want to return, but not what
// we need to write the padding and offset of this tag
let answer = build_clone(
env,
layout_interner,
layout_ids,
ptr,
cursors,
payload,
payload_in_layout,
);
// include padding between data and tag id
let tag_id_internal_offset = roc_union.data_width();
let tag_id_offset = offset_add(env.builder, cursors.offset, tag_id_internal_offset);
// write the tag id
let value = env.context.i8_type().const_int(tag_id as _, false);
build_copy(env, ptr, tag_id_offset, value.into());
// NOTE: padding after tag id (is taken care of by the cursor)
answer
}
fn build_clone_tag_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
union_layout: UnionLayout<'a>,
when_recursive: WhenRecursive<'a>,
fn_val: FunctionValue<'ctx>,
) {
use bumpalo::collections::Vec;
@ -629,29 +681,37 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let layout = layout_interner.insert(Layout::struct_no_name_order(
env.arena.alloc([layout, union_layout.tag_id_layout()]),
));
let basic_type = basic_type_from_layout(env, layout_interner, layout);
let data = load_tag_data(
env,
let roc_union = RocUnion::tagged_from_slices(
layout_interner,
union_layout,
tag_value.into_pointer_value(),
basic_type,
env.context,
tags,
env.target_info,
);
let answer = build_clone(
// load the tag payload (if any)
let payload_layout = Layout::struct_no_name_order(field_layouts);
let payload_in_layout = layout_interner.insert(payload_layout);
let opaque_payload_ptr = env
.builder
.new_build_struct_gep(
roc_union.struct_type(),
tag_value.into_pointer_value(),
RocUnion::TAG_DATA_INDEX,
"data_buffer",
)
.unwrap();
let answer = clone_tag_payload_and_id(
env,
layout_interner,
layout_ids,
ptr,
cursors,
data,
layout,
when_recursive,
roc_union,
tag_id,
payload_in_layout,
opaque_payload_ptr,
);
env.builder.build_return(Some(&answer));
@ -712,17 +772,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
),
};
let when_recursive = WhenRecursive::Loop(union_layout);
let answer = build_clone(
env,
layout_interner,
layout_ids,
ptr,
cursors,
data,
layout,
when_recursive,
);
let answer =
build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
@ -762,17 +813,7 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let data = load_tag_data(env, layout_interner, union_layout, tag_value, basic_type);
let when_recursive = WhenRecursive::Loop(union_layout);
let answer = build_clone(
env,
layout_interner,
layout_ids,
ptr,
cursors,
data,
layout,
when_recursive,
);
let answer = build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
}
@ -831,17 +872,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let data =
load_tag_data(env, layout_interner, union_layout, tag_value, basic_type);
let when_recursive = WhenRecursive::Loop(union_layout);
let answer = build_clone(
env,
layout_interner,
layout_ids,
ptr,
cursors,
data,
layout,
when_recursive,
);
let answer =
build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
@ -917,17 +949,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
basic_type,
);
let when_recursive = WhenRecursive::Loop(union_layout);
let answer = build_clone(
env,
layout_interner,
layout_ids,
ptr,
cursors,
data,
layout,
when_recursive,
);
let answer =
build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
}
@ -978,7 +1001,7 @@ fn build_copy<'a, 'ctx, 'env>(
)
};
let ptr_type = value.get_type().ptr_type(AddressSpace::Generic);
let ptr_type = value.get_type().ptr_type(AddressSpace::default());
let ptr = env
.builder
.build_pointer_cast(ptr, ptr_type, "cast_ptr_type");
@ -997,7 +1020,6 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
builtin: Builtin<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
use Builtin::*;
@ -1051,7 +1073,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
let dest = pointer_at_offset(bd, env.context.i8_type(), ptr, elements_start_offset);
let src = bd.build_pointer_cast(
elements,
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"to_bytes_pointer",
);
bd.build_memcpy(dest, 1, src, 1, elements_width).unwrap();
@ -1061,7 +1083,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
let element_type = basic_type_from_layout(env, layout_interner, elem);
let elements = bd.build_pointer_cast(
elements,
element_type.ptr_type(AddressSpace::Generic),
element_type.ptr_type(AddressSpace::default()),
"elements",
);
@ -1102,7 +1124,6 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
cursors,
element,
elem,
when_recursive,
);
bd.build_store(rest_offset, new_offset);

View file

@ -4,7 +4,6 @@ use crate::llvm::build::{CCReturn, Env, FunctionSpec};
use crate::llvm::convert::zig_str_type;
use inkwell::module::Linkage;
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use inkwell::AddressSpace;
use roc_builtins::bitcode;
@ -19,7 +18,7 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
let builder = env.builder;
let usize_type = env.ptr_int();
let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::default());
match env.mode {
super::build::LlvmBackendMode::CliTest => {
@ -198,6 +197,11 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
let mut params = fn_val.get_param_iter();
let roc_str_arg = params.next().unwrap();
// normally, roc_panic is marked as external so it can be provided by the host. But when we
// define it here in LLVM IR, we never want it to be linked by the host (that would
// overwrite which implementation is used.
fn_val.set_linkage(Linkage::Internal);
let tag_id_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
@ -271,7 +275,7 @@ pub fn build_longjmp_call(env: &Env) {
// Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)`
let jmp_buf_i8p = env.builder.build_pointer_cast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"jmp_buf i8*",
);
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p.into()]);

View file

@ -75,7 +75,7 @@ pub(crate) fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
let i1_type = ctx.bool_type();
let i8_type = ctx.i8_type();
let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic);
let i8_ptr_type = i8_type.ptr_type(AddressSpace::default());
let i32_type = ctx.i32_type();
let void_type = ctx.void_type();

View file

@ -13,6 +13,7 @@ use roc_module::{low_level::LowLevel, symbol::Symbol};
use roc_mono::{
ir::HigherOrderLowLevel,
layout::{Builtin, InLayout, LambdaSet, Layout, LayoutIds, LayoutInterner, STLayoutInterner},
list_element_layout,
};
use roc_target::PtrWidth;
@ -27,10 +28,10 @@ use crate::llvm::{
load_roc_value, roc_function_call, BuilderExt, RocReturn,
},
build_list::{
list_append_unsafe, list_capacity, list_concat, list_drop_at, list_get_unsafe, list_len,
list_map, list_map2, list_map3, list_map4, list_prepend, list_replace_unsafe, list_reserve,
list_sort_with, list_sublist, list_swap, list_symbol_to_c_abi, list_with_capacity,
pass_update_mode,
list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map,
list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity,
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
list_symbol_to_c_abi, list_with_capacity, pass_update_mode,
},
compare::{generic_eq, generic_neq},
convert::{
@ -49,15 +50,6 @@ use super::{
convert::zig_dec_type,
};
macro_rules! list_element_layout {
($interner:expr, $list_layout:expr) => {
match $interner.get($list_layout) {
Layout::Builtin(Builtin::List(list_layout)) => list_layout,
_ => unreachable!("invalid list layout"),
}
};
}
pub(crate) fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
@ -256,7 +248,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
let roc_return_type =
basic_type_from_layout(env, layout_interner, layout)
.ptr_type(AddressSpace::Generic);
.ptr_type(AddressSpace::default());
let roc_return_alloca = env.builder.build_pointer_cast(
zig_return_alloca,
@ -497,7 +489,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
let return_type = basic_type_from_layout(env, layout_interner, layout);
let cast_result = env.builder.build_pointer_cast(
result,
return_type.ptr_type(AddressSpace::Generic),
return_type.ptr_type(AddressSpace::default()),
"cast",
);
@ -567,6 +559,18 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
bitcode::STR_RESERVE,
)
}
StrReleaseExcessCapacity => {
// Str.releaseExcessCapacity: Str -> Str
arguments!(string);
call_str_bitcode_fn(
env,
&[string],
&[],
BitcodeReturns::Str,
bitcode::STR_RELEASE_EXCESS_CAPACITY,
)
}
StrAppendScalar => {
// Str.appendScalar : Str, U32 -> Str
arguments!(string, capacity);
@ -640,10 +644,16 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
list_len(env.builder, list.into_struct_value()).into()
}
ListGetCapacity => {
// List.capacity : List * -> Nat
// List.capacity: List a -> Nat
arguments!(list);
list_capacity(env.builder, list.into_struct_value()).into()
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[],
BitcodeReturns::Basic,
bitcode::LIST_CAPACITY,
)
}
ListWithCapacity => {
// List.withCapacity : Nat -> List a
@ -709,6 +719,15 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
update_mode,
)
}
ListReleaseExcessCapacity => {
// List.releaseExcessCapacity: List elem -> List elem
debug_assert_eq!(args.len(), 1);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let element_layout = list_element_layout!(layout_interner, list_layout);
list_release_excess_capacity(env, layout_interner, list, element_layout, update_mode)
}
ListSwap => {
// List.swap : List elem, Nat, Nat -> List elem
debug_assert_eq!(args.len(), 3);
@ -856,9 +875,24 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
_ => unreachable!(),
}
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
| NumCeiling | NumFloor | NumToFrac | NumIsFinite | NumAtan | NumAcos | NumAsin
| NumToIntChecked => {
NumAbs
| NumNeg
| NumRound
| NumSqrtUnchecked
| NumLogUnchecked
| NumSin
| NumCos
| NumCeiling
| NumFloor
| NumToFrac
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumToIntChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => {
arguments_with_layouts!((arg, arg_layout));
match layout_interner.get(arg_layout) {
@ -922,6 +956,28 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
bitcode::NUM_BYTES_TO_U32,
)
}
NumBytesToU64 => {
arguments!(list, position);
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U64,
)
}
NumBytesToU128 => {
arguments!(list, position);
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U128,
)
}
NumCompare => {
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
@ -1663,7 +1719,7 @@ fn dec_alloca<'a, 'ctx, 'env>(
let ptr = env.builder.build_pointer_cast(
alloca,
value.get_type().ptr_type(AddressSpace::Generic),
value.get_type().ptr_type(AddressSpace::default()),
"cast_to_i128_ptr",
);
@ -2013,7 +2069,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
let roc_return_type =
basic_type_from_layout(env, layout_interner, return_layout)
.ptr_type(AddressSpace::Generic);
.ptr_type(AddressSpace::default());
let roc_return_alloca = env.builder.build_pointer_cast(
zig_return_alloca,
@ -2053,6 +2109,19 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked")
}
}
NumCountLeadingZeroBits => call_bitcode_fn(
env,
&[arg.into()],
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width],
),
NumCountTrailingZeroBits => call_bitcode_fn(
env,
&[arg.into()],
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width],
),
NumCountOneBits => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width])
}
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}

View file

@ -3,17 +3,18 @@ use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::BuilderExt;
use crate::llvm::build::{
add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env,
WhenRecursive, FAST_CALL_CONV,
FAST_CALL_CONV,
};
use crate::llvm::build_list::{incrementing_elem_loop, list_capacity, load_list};
use crate::llvm::build_list::{
incrementing_elem_loop, list_capacity_or_ref_ptr, list_refcount_ptr, load_list,
};
use crate::llvm::build_str::str_refcount_ptr;
use crate::llvm::convert::{basic_type_from_layout, zig_str_type, RocUnion};
use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
@ -40,7 +41,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let value = env.builder.build_pointer_cast(
ptr,
refcount_type.ptr_type(AddressSpace::Generic),
refcount_type.ptr_type(AddressSpace::default()),
"to_refcount_ptr",
);
@ -54,7 +55,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let builder = env.builder;
// pointer to usize
let refcount_type = env.ptr_int();
let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::Generic);
let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::default());
let ptr_as_usize_ptr =
builder.build_pointer_cast(data_ptr, refcount_ptr_type, "as_usize_ptr");
@ -75,16 +76,6 @@ impl<'ctx> PointerToRefcount<'ctx> {
}
}
fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
let data_ptr = env
.builder
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_pointer_value();
Self::from_ptr_to_data(env, data_ptr)
}
pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
let current = self.get_refcount(env);
let one = match env.target_info.ptr_width() {
@ -148,7 +139,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
None => {
// inc and dec return void
let fn_type = context.void_type().fn_type(
&[env.ptr_int().ptr_type(AddressSpace::Generic).into()],
&[env.ptr_int().ptr_type(AddressSpace::default()).into()],
false,
);
@ -172,8 +163,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let refcount_ptr = self.value;
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
let call = env
.builder
@ -216,7 +206,7 @@ fn incref_pointer<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::Generic),
env.ptr_int().ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
@ -238,7 +228,7 @@ fn decref_pointer<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::Generic),
env.ptr_int().ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
@ -261,7 +251,7 @@ pub fn decref_pointer_check_null<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
pointer,
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i8_type().ptr_type(AddressSpace::default()),
"to_i8_ptr",
)
.into(),
@ -277,7 +267,6 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
layouts: &'a [InLayout<'a>],
mode: Mode,
when_recursive: &WhenRecursive<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
@ -304,7 +293,6 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
when_recursive,
layouts,
function_value,
);
@ -314,8 +302,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function
}
@ -326,7 +313,6 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layouts: &[InLayout<'a>],
fn_val: FunctionValue<'ctx>,
) {
@ -368,7 +354,6 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
field_value,
*field_layout,
);
@ -430,7 +415,6 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: InLayout<'a>,
builtin: &Builtin<'a>,
) -> Option<FunctionValue<'ctx>> {
@ -438,14 +422,8 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
match builtin {
List(element_layout) => {
let function = modify_refcount_list(
env,
layout_interner,
layout_ids,
mode,
when_recursive,
*element_layout,
);
let function =
modify_refcount_list(env, layout_interner, layout_ids, mode, *element_layout);
Some(function)
}
@ -473,15 +451,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>,
layout: InLayout<'a>,
) {
modify_refcount_layout_help(
env,
layout_interner,
layout_ids,
call_mode,
&WhenRecursive::Unreachable,
value,
layout,
);
modify_refcount_layout_help(env, layout_interner, layout_ids, call_mode, value, layout);
}
fn modify_refcount_layout_help<'a, 'ctx, 'env>(
@ -489,7 +459,6 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
call_mode: CallMode<'ctx>,
when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>,
layout: InLayout<'a>,
) {
@ -498,38 +467,28 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
CallMode::Dec => Mode::Dec,
};
let function = match modify_refcount_layout_build_function(
env,
layout_interner,
layout_ids,
mode,
when_recursive,
layout,
) {
Some(f) => f,
None => return,
};
let function =
match modify_refcount_layout_build_function(env, layout_interner, layout_ids, mode, layout)
{
Some(f) => f,
None => return,
};
match layout_interner.get(layout) {
Layout::RecursivePointer(_) => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = layout_interner.insert(Layout::Union(*union_layout));
Layout::RecursivePointer(rec_layout) => {
let layout = rec_layout;
let bt = basic_type_from_layout(env, layout_interner, layout);
let bt = basic_type_from_layout(env, layout_interner, layout);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env.builder.build_pointer_cast(
value.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env.builder.build_pointer_cast(
value.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
call_help(env, function, call_mode, field_cast.into());
}
},
call_help(env, function, call_mode, field_cast.into());
}
_ => {
call_help(env, function, call_mode, value);
}
@ -568,21 +527,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: InLayout<'a>,
) -> Option<FunctionValue<'ctx>> {
use Layout::*;
match layout_interner.get(layout) {
Builtin(builtin) => modify_refcount_builtin(
env,
layout_interner,
layout_ids,
mode,
when_recursive,
layout,
&builtin,
),
Builtin(builtin) => {
modify_refcount_builtin(env, layout_interner, layout_ids, mode, layout, &builtin)
}
Boxed(inner) => {
let function = modify_refcount_boxed(env, layout_interner, layout_ids, mode, inner);
@ -600,27 +552,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
NonRecursive(tags) => {
let function = modify_refcount_nonrecursive(
env,
layout_interner,
layout_ids,
mode,
when_recursive,
tags,
);
let function =
modify_refcount_nonrecursive(env, layout_interner, layout_ids, mode, tags);
Some(function)
}
_ => {
let function = build_rec_union(
env,
layout_interner,
layout_ids,
mode,
&WhenRecursive::Loop(variant),
variant,
);
let function = build_rec_union(env, layout_interner, layout_ids, mode, variant);
Some(function)
}
@ -628,43 +567,30 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
Struct { field_layouts, .. } => {
let function = modify_refcount_struct(
env,
layout_interner,
layout_ids,
field_layouts,
mode,
when_recursive,
);
let function =
modify_refcount_struct(env, layout_interner, layout_ids, field_layouts, mode);
Some(function)
}
Layout::RecursivePointer(_) => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers cannot be in/decremented directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = layout_interner.insert(Layout::Union(*union_layout));
Layout::RecursivePointer(rec_layout) => {
let layout = rec_layout;
let function = modify_refcount_layout_build_function(
env,
layout_interner,
layout_ids,
mode,
when_recursive,
layout,
)?;
let function = modify_refcount_layout_build_function(
env,
layout_interner,
layout_ids,
mode,
layout,
)?;
Some(function)
}
},
Some(function)
}
LambdaSet(lambda_set) => modify_refcount_layout_build_function(
env,
layout_interner,
layout_ids,
mode,
when_recursive,
lambda_set.runtime_representation(),
),
}
@ -675,15 +601,18 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
element_layout: InLayout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let element_layout = layout_interner.get(element_layout);
let element_layout = when_recursive.unwrap_recursive_pointer(element_layout);
let element_layout = layout_interner.insert(element_layout);
let element_layout = if let Layout::RecursivePointer(rec) = layout_interner.get(element_layout)
{
rec
} else {
element_layout
};
let list_layout = layout_interner.insert(Layout::Builtin(Builtin::List(element_layout)));
let (_, fn_name) = function_name_from_mode(
layout_ids,
@ -705,7 +634,6 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
when_recursive,
list_layout,
element_layout,
function_value,
@ -716,8 +644,7 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function
}
@ -734,7 +661,6 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: InLayout<'a>,
element_layout: InLayout<'a>,
fn_val: FunctionValue<'ctx>,
@ -758,26 +684,27 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
let parent = fn_val;
let original_wrapper = arg_val.into_struct_value();
let len = list_capacity(builder, original_wrapper);
// We use the raw capacity to ensure we always decrement the refcount of seamless slices.
let capacity = list_capacity_or_ref_ptr(builder, original_wrapper);
let is_non_empty = builder.build_int_compare(
IntPredicate::UGT,
len,
capacity,
env.ptr_int().const_zero(),
"len > 0",
"cap > 0",
);
// build blocks
let modification_block = ctx.append_basic_block(parent, "modification_block");
let modification_list_block = ctx.append_basic_block(parent, "modification_list_block");
let cont_block = ctx.append_basic_block(parent, "modify_rc_list_cont");
builder.build_conditional_branch(is_non_empty, modification_block, cont_block);
builder.build_conditional_branch(is_non_empty, modification_list_block, cont_block);
builder.position_at_end(modification_block);
builder.position_at_end(modification_list_block);
if layout_interner.contains_refcounted(element_layout) {
let ptr_type = basic_type_from_layout(env, layout_interner, element_layout)
.ptr_type(AddressSpace::Generic);
.ptr_type(AddressSpace::default());
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
@ -787,7 +714,6 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
element,
element_layout,
);
@ -805,7 +731,8 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
);
}
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
let refcount_ptr =
PointerToRefcount::from_ptr_to_data(env, list_refcount_ptr(env, original_wrapper));
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env, layout_interner);
@ -849,8 +776,7 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function
}
@ -880,9 +806,9 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
let parent = fn_val;
let arg_val =
let str_type = zig_str_type(env);
let str_wrapper =
if Layout::Builtin(Builtin::Str).is_passed_by_reference(layout_interner, env.target_info) {
let str_type = zig_str_type(env);
env.builder
.new_build_load(str_type, arg_val.into_pointer_value(), "load_str_to_stack")
} else {
@ -890,7 +816,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
debug_assert!(arg_val.is_struct_value());
arg_val
};
let str_wrapper = arg_val.into_struct_value();
let str_wrapper = str_wrapper.into_struct_value();
let capacity = builder
.build_extract_value(str_wrapper, Builtin::WRAPPER_CAPACITY, "read_str_capacity")
@ -913,7 +839,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
builder.build_conditional_branch(is_big_and_non_empty, modification_block, cont_block);
builder.position_at_end(modification_block);
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, str_wrapper);
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, str_refcount_ptr(env, arg_val));
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env, layout_interner);
@ -959,8 +885,7 @@ fn modify_refcount_boxed<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function
}
@ -1093,7 +1018,6 @@ fn build_rec_union<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>,
) -> FunctionValue<'ctx> {
let layout = layout_interner.insert(Layout::Union(union_layout));
@ -1121,14 +1045,12 @@ fn build_rec_union<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
when_recursive,
union_layout,
function_value,
);
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function_value
}
@ -1143,7 +1065,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>,
fn_val: FunctionValue<'ctx>,
) {
@ -1235,7 +1156,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
parent,
fn_val,
union_layout,
@ -1268,7 +1188,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: &WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
decrement_fn: FunctionValue<'ctx>,
union_layout: UnionLayout<'a>,
@ -1316,7 +1235,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = env.builder.build_pointer_cast(
value_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
wrapper_type.ptr_type(AddressSpace::default()),
"opaque_to_correct_recursive_decrement",
);
@ -1339,7 +1258,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
.unwrap();
let ptr_as_i64_ptr = env.builder.new_build_load(
env.context.i64_type().ptr_type(AddressSpace::Generic),
env.context.i64_type().ptr_type(AddressSpace::default()),
elem_pointer,
"load_recursive_pointer",
);
@ -1396,7 +1315,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(decrement_fn),
when_recursive,
field,
*field_layout,
);
@ -1422,11 +1340,16 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
union_layout,
UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }
) {
debug_assert_eq!(cases.len(), 1);
debug_assert!(cases.len() <= 1, "{cases:?}");
// in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id
let (_, only_branch) = cases.pop().unwrap();
env.builder.build_unconditional_branch(only_branch);
if cases.is_empty() {
// The only other layout doesn't need refcounting. Pass through.
builder.build_return(None);
} else {
// in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id
let (_, only_branch) = cases.pop().unwrap();
env.builder.build_unconditional_branch(only_branch);
}
} else {
let default_block = env.context.append_basic_block(parent, "switch_default");
@ -1449,6 +1372,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
}
}
#[derive(Debug)]
struct UnionLayoutTags<'a> {
nullable_id: Option<u16>,
tags: &'a [&'a [InLayout<'a>]],
@ -1503,15 +1427,7 @@ pub fn build_reset<'a, 'ctx, 'env>(
let fn_name = layout_id.to_symbol_string(Symbol::DEC, &env.interns);
let fn_name = format!("{}_reset", fn_name);
let when_recursive = WhenRecursive::Loop(union_layout);
let dec_function = build_rec_union(
env,
layout_interner,
layout_ids,
Mode::Dec,
&when_recursive,
union_layout,
);
let dec_function = build_rec_union(env, layout_interner, layout_ids, Mode::Dec, union_layout);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -1526,15 +1442,13 @@ pub fn build_reset<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
&when_recursive,
union_layout,
function_value,
dec_function,
);
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function_value
}
@ -1548,7 +1462,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>,
reset_function: FunctionValue<'ctx>,
dec_function: FunctionValue<'ctx>,
@ -1626,7 +1539,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
when_recursive,
parent,
dec_function,
union_layout,
@ -1665,7 +1577,6 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [InLayout<'a>]],
) -> FunctionValue<'ctx> {
let union_layout = UnionLayout::NonRecursive(fields);
@ -1694,7 +1605,6 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
when_recursive,
fields,
function_value,
);
@ -1704,8 +1614,7 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
env.builder.set_current_debug_location(di_location);
function
}
@ -1715,7 +1624,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &'a [&'a [InLayout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
@ -1805,19 +1713,12 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
let cast_tag_data_pointer = env.builder.build_pointer_cast(
opaque_tag_data_ptr,
data_struct_type.ptr_type(AddressSpace::Generic),
data_struct_type.ptr_type(AddressSpace::default()),
"cast_to_concrete_tag",
);
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer(_) = layout_interner.get(*field_layout) {
let recursive_union_layout = match when_recursive {
WhenRecursive::Unreachable => {
panic!("non-recursive tag unions cannot contain naked recursion pointers!");
}
WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout,
};
if let Layout::RecursivePointer(union_layout) = layout_interner.get(*field_layout) {
// This field is a pointer to the recursive pointer.
let field_ptr = env
.builder
@ -1831,7 +1732,7 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
// This is the actual pointer to the recursive data.
let field_value = env.builder.new_build_load(
env.context.i64_type().ptr_type(AddressSpace::Generic),
env.context.i64_type().ptr_type(AddressSpace::default()),
field_ptr,
"load_recursive_pointer",
);
@ -1839,7 +1740,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
debug_assert!(field_value.is_pointer_value());
// therefore we must cast it to our desired type
let union_layout = layout_interner.insert(Layout::Union(*recursive_union_layout));
let union_type = basic_type_from_layout(env, layout_interner, union_layout);
let recursive_ptr_field_value =
cast_basic_basic(env.builder, field_value, union_type);
@ -1849,7 +1749,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
recursive_ptr_field_value,
*field_layout,
)
@ -1879,7 +1778,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
field_value,
*field_layout,
);

View file

@ -1,19 +1,20 @@
[package]
name = "roc_gen_wasm"
version = "0.0.1"
edition = "2021"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
description = "Provides the WASM backend to generate Roc binaries."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" }
roc_error_macros = { path = "../../error_macros" }
roc_target = { path = "../roc_target" }
roc_wasm_module = { path = "../../wasm_module" }
bitvec.workspace = true

View file

@ -285,6 +285,9 @@ impl<'a> LowLevelCall<'a> {
StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT),
StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8),
StrReserve => self.load_args_and_call_zig(backend, bitcode::STR_RESERVE),
StrReleaseExcessCapacity => {
self.load_args_and_call_zig(backend, bitcode::STR_RELEASE_EXCESS_CAPACITY)
}
StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT),
StrAppendScalar => self.load_args_and_call_zig(backend, bitcode::STR_APPEND_SCALAR),
StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM),
@ -318,25 +321,7 @@ impl<'a> LowLevelCall<'a> {
_ => internal_error!("invalid storage for List"),
},
ListGetCapacity => match backend.storage.get(&self.arguments[0]) {
StoredValue::StackMemory { location, .. } => {
let (local_id, offset) =
location.local_and_offset(backend.storage.stack_frame_pointer);
backend.code_builder.get_local(local_id);
// List is stored as (pointer, length, capacity),
// with each of those fields being 4 bytes on wasm.
// So the capacity is 8 bytes after the start of the struct.
//
// WRAPPER_CAPACITY represents the index of the capacity field
// (which is 2 as of the writing of this comment). If the field order
// ever changes, WRAPPER_CAPACITY should be updated and this logic should
// continue to work even though this comment may become inaccurate.
backend
.code_builder
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_CAPACITY));
}
_ => internal_error!("invalid storage for List"),
},
ListGetCapacity => self.load_args_and_call_zig(backend, bitcode::LIST_CAPACITY),
ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE),
@ -573,6 +558,46 @@ impl<'a> LowLevelCall<'a> {
backend.call_host_fn_after_loading_args(bitcode::LIST_RESERVE, 7, false);
}
ListReleaseExcessCapacity => {
// List.releaseExcessCapacity : List elem -> List elem
let list: Symbol = self.arguments[0];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get(elem_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner, TARGET_INFO);
// Zig arguments Wasm types
// (return pointer) i32
// list: RocList i64, i32
// alignment: u32 i32
// element_width: usize i32
// update_mode: UpdateMode i32
// return pointer and list
backend.storage.load_symbols_for_call(
backend.env.arena,
&mut backend.code_builder,
&[list],
self.ret_symbol,
&WasmLayout::new(backend.layout_interner, self.ret_layout),
CallConv::Zig,
);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
backend.call_host_fn_after_loading_args(
bitcode::LIST_RELEASE_EXCESS_CAPACITY,
6,
false,
);
}
ListAppendUnsafe => {
// List.append : List elem, elem -> List elem
@ -1489,6 +1514,40 @@ impl<'a> LowLevelCall<'a> {
}
_ => panic_ret_type(),
},
NumCountLeadingZeroBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(
backend,
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[width],
);
}
_ => panic_ret_type(),
},
NumCountTrailingZeroBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(
backend,
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[width],
);
}
_ => panic_ret_type(),
},
NumCountOneBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(backend, &bitcode::NUM_COUNT_ONE_BITS[width]);
}
_ => panic_ret_type(),
},
NumRound => {
self.load_args(backend);
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
@ -1577,6 +1636,8 @@ impl<'a> LowLevelCall<'a> {
},
NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16),
NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32),
NumBytesToU64 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U64),
NumBytesToU128 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U128),
NumBitwiseAnd => {
self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) {

View file

@ -8,7 +8,7 @@ use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Builtin, InLayout, Layout, LayoutInterner, UnionLayout};
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
use roc_wasm_module::{
linking::SymInfo, linking::WasmObjectSymbol, Align, Export, ExportType, LocalId, Signature,
ValueType, WasmModule,
@ -203,6 +203,13 @@ impl<T: Wasm32Result> Wasm32Result for RocList<T> {
}
}
impl<T: Wasm32Result> Wasm32Result for RocBox<T> {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
// treat box as if it's just a isize value
<i32 as Wasm32Result>::build_wrapper_body(code_builder, main_function_index)
}
}
impl<T: Wasm32Sized, E: Wasm32Sized> Wasm32Result for RocResult<T, E> {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(

View file

@ -1,4 +1,4 @@
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
pub trait Wasm32Sized: Sized {
const SIZE_OF_WASM: usize;
@ -45,6 +45,11 @@ impl<T: Wasm32Sized> Wasm32Sized for RocList<T> {
const ALIGN_OF_WASM: usize = 4;
}
impl<T: Wasm32Sized> Wasm32Sized for RocBox<T> {
const SIZE_OF_WASM: usize = 4;
const ALIGN_OF_WASM: usize = 4;
}
impl<T: Wasm32Sized, E: Wasm32Sized> Wasm32Sized for RocResult<T, E> {
const ALIGN_OF_WASM: usize = max(&[T::ALIGN_OF_WASM, E::ALIGN_OF_WASM]);
const SIZE_OF_WASM: usize = max(&[T::ACTUAL_WIDTH, E::ACTUAL_WIDTH]) + 1;

View file

@ -1,7 +1,8 @@
[package]
name = "roc_ident"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Implements data structures used for efficiently representing small strings, like identifiers."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true

View file

@ -1,19 +1,20 @@
[package]
name = "roc_late_solve"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides type unification and solving primitives from the perspective of the compiler backend."
[dependencies]
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_derive = { path = "../derive" }
roc_module = { path = "../module" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
bumpalo.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_derive = { path = "../derive" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_solve = { path = "../solve" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
bumpalo.workspace = true

View file

@ -20,7 +20,7 @@ use roc_types::types::Polarity;
use roc_unify::unify::MetaCollector;
use roc_unify::unify::{Env, Mode, Unified};
pub use roc_solve::ability::Resolved;
pub use roc_solve::ability::{ResolveError, Resolved};
pub use roc_types::subs::instantiate_rigids;
pub mod storage;
@ -161,7 +161,7 @@ pub fn resolve_ability_specialization(
abilities: &AbilitiesView,
ability_member: Symbol,
specialization_var: Variable,
) -> Option<Resolved> {
) -> Result<Resolved, ResolveError> {
let late_resolver = LateResolver { home, abilities };
roc_solve::ability::resolve_ability_specialization(
subs,

View file

@ -1,30 +1,31 @@
[package]
name = "roc_load"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Used to load a .roc file and coordinate the compiler pipeline, including parsing, type checking, and code generation."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_load_internal = { path = "../load_internal" }
roc_target = { path = "../roc_target" }
roc_can = { path = "../can" }
roc_types = { path = "../types" }
roc_module = { path = "../module" }
roc_collections = { path = "../collections" }
roc_load_internal = { path = "../load_internal" }
roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
bumpalo.workspace = true
[build-dependencies]
roc_builtins = { path = "../builtins" }
roc_can = { path = "../can" }
roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" }
roc_can = { path = "../can" }
bumpalo.workspace = true

Some files were not shown because too many files have changed in this diff Show more