Merge remote-tracking branch 'origin/trunk' into outdent-infix

This commit is contained in:
Richard Feldman 2022-07-16 16:08:36 -04:00
commit f575807834
No known key found for this signature in database
GPG key ID: 7E4127D1E4241798
116 changed files with 7901 additions and 5073 deletions

94
Cargo.lock generated
View file

@ -2105,6 +2105,22 @@ dependencies = [
"libc",
]
[[package]]
name = "mach_object"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6f2d7176b94027af58085a2c9d27c4e416586caba409c314569213901d6068"
dependencies = [
"bitflags",
"byteorder",
"lazy_static",
"libc",
"log",
"thiserror",
"time 0.3.11",
"uuid",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@ -2460,6 +2476,15 @@ dependencies = [
"syn",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "objc"
version = "0.2.7"
@ -2499,6 +2524,18 @@ dependencies = [
"objc",
]
[[package]]
name = "object"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
dependencies = [
"crc32fast",
"flate2",
"indexmap",
"memchr",
]
[[package]]
name = "object"
version = "0.28.4"
@ -3444,10 +3481,12 @@ dependencies = [
"roc_parse",
"roc_region",
"roc_repl_cli",
"roc_repl_expect",
"roc_reporting",
"roc_target",
"roc_test_utils",
"serial_test",
"signal-hook",
"strum",
"strum_macros",
"target-lexicon",
@ -3520,6 +3559,7 @@ dependencies = [
name = "roc_derive_key"
version = "0.1.0"
dependencies = [
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_module",
@ -3714,7 +3754,8 @@ dependencies = [
"bumpalo",
"roc_can",
"roc_collections",
"roc_derive_key",
"roc_derive",
"roc_error_macros",
"roc_module",
"roc_solve",
"roc_types",
@ -3729,10 +3770,12 @@ dependencies = [
"bumpalo",
"clap 3.2.8",
"iced-x86",
"mach_object",
"memmap2 0.5.4",
"object 0.29.0",
"object 0.26.2",
"roc_build",
"roc_collections",
"roc_error_macros",
"roc_mono",
"serde",
"target-lexicon",
@ -3769,6 +3812,7 @@ dependencies = [
"roc_collections",
"roc_constrain",
"roc_debug_flags",
"roc_derive",
"roc_derive_key",
"roc_error_macros",
"roc_late_solve",
@ -3810,7 +3854,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_debug_flags",
"roc_derive_key",
"roc_derive",
"roc_error_macros",
"roc_exhaustive",
"roc_late_solve",
@ -3959,7 +4003,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_derive_key",
"roc_derive",
"roc_exhaustive",
"roc_load",
"roc_module",
@ -3988,6 +4032,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_debug_flags",
"roc_derive",
"roc_derive_key",
"roc_error_macros",
"roc_exhaustive",
@ -4378,6 +4423,25 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal-hook"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "similar"
version = "2.1.0"
@ -4827,11 +4891,23 @@ dependencies = [
"libc",
"standback",
"stdweb 0.4.20",
"time-macros",
"time-macros 0.1.1",
"version_check",
"winapi",
]
[[package]]
name = "time"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
dependencies = [
"itoa 1.0.2",
"libc",
"num_threads",
"time-macros 0.2.4",
]
[[package]]
name = "time-macros"
version = "0.1.1"
@ -4842,6 +4918,12 @@ dependencies = [
"time-macros-impl",
]
[[package]]
name = "time-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "time-macros-impl"
version = "0.1.2"
@ -5453,7 +5535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22dc83aadbdf97388de3211cb6f105374f245a3cf2a5c65a16776e7a087a8468"
dependencies = [
"byteorder",
"time",
"time 0.2.27",
"wasmer-types",
]

View file

@ -4,7 +4,7 @@ use crate::lang::core::{expr::expr2::ExprId, header::AppHeader};
pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader {
AppHeader {
app_name: "\"untitled-app\"".to_owned(),
packages_base: "\"c-platform/main.roc\"".to_owned(),
packages_base: "\"platform/main.roc\"".to_owned(),
imports: vec![],
provides: vec!["main".to_owned()],
ast_node_id,

View file

@ -266,6 +266,9 @@ fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impl
// This is recursively pointing to a type that should already have been added,
// so no extra work needs to happen.
}
RocType::Function(_, _) => {
// TODO actually bindgen functions!
}
}
}
@ -375,7 +378,7 @@ pub struct {name} {{
write!(buf, "{INDENT}{tag_name}: ").unwrap();
if payload_type.has_pointer(types) {
if cannot_derive_copy(payload_type, types) {
// types with pointers need ManuallyDrop
// because rust unions don't (and can't)
// know how to drop them automatically!
@ -553,7 +556,7 @@ pub struct {name} {{
match recursiveness {
Recursiveness::Recursive => {
if payload_type.has_pointer(types) {
if cannot_derive_copy(payload_type, types) {
owned_get_payload = format!(
r#"{{
let ptr = (self.pointer as usize & !{bitmask}) as *mut {union_name};
@ -590,7 +593,7 @@ pub struct {name} {{
};
}
Recursiveness::NonRecursive => {
if payload_type.has_pointer(types) {
if cannot_derive_copy(payload_type, types) {
owned_get_payload =
format!("core::mem::ManuallyDrop::take(&mut self.{tag_name})");
borrowed_get_payload = format!("&self.{tag_name}");
@ -620,7 +623,7 @@ pub struct {name} {{
owned_ret = "payload".to_string();
borrowed_ret = format!("&{owned_ret}");
payload_args = format!("arg: {owned_ret_type}");
args_to_payload = if payload_type.has_pointer(types) {
args_to_payload = if cannot_derive_copy(payload_type, types) {
"core::mem::ManuallyDrop::new(arg)".to_string()
} else {
"arg".to_string()
@ -648,6 +651,7 @@ pub struct {name} {{
payload_args = answer.payload_args;
args_to_payload = answer.args_to_payload;
}
RocType::Function(_, _) => todo!(),
};
{
@ -790,7 +794,7 @@ pub struct {name} {{
&mut drop_payload,
|tag_name, opt_payload_id| {
match opt_payload_id {
Some(payload_id) if types.get_type(payload_id).has_pointer(types) => {
Some(payload_id) if cannot_derive_copy(types.get_type(payload_id), types) => {
format!("unsafe {{ core::mem::ManuallyDrop::drop(&mut {actual_self_mut}.{tag_name}) }},",)
}
_ => {
@ -845,7 +849,7 @@ pub struct {name} {{
// The PartialEq impl for the tag union
{
let opt_impl_prefix = if typ.has_float(types) {
let opt_impl_prefix = if has_float(typ, types) {
String::new()
} else {
format!("impl Eq for {name} {{}}\n\n")
@ -966,7 +970,7 @@ pub struct {name} {{
// The Clone impl for the tag union
{
let opt_impl_prefix = if typ.has_pointer(types) {
let opt_impl_prefix = if cannot_derive_copy(typ, types) {
String::new()
} else {
format!("impl Copy for {name} {{}}\n\n")
@ -1089,7 +1093,7 @@ pub struct {name} {{
// (because otherwise we're using ManuallyDrop's Debug instance
// rather than the Debug instance of the value it wraps).
let payload_type = types.get_type(payload_id);
let deref_str = if payload_type.has_pointer(types) {
let deref_str = if cannot_derive_copy(payload_type, types) {
"&*"
} else {
"&"
@ -1120,6 +1124,7 @@ pub struct {name} {{
buf.join("\n")
}
RocType::Function(_, _) => todo!(),
};
format!(
@ -1286,6 +1291,7 @@ fn type_name(id: TypeId, types: &Types) -> String {
| RocType::TagUnion(RocTagUnion::NullableUnwrapped { name, .. })
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { name, .. }) => name.clone(),
RocType::RecursivePointer(content) => type_name(*content, types),
RocType::Function(_, _) => todo!(),
}
}
@ -1295,7 +1301,7 @@ fn type_name(id: TypeId, types: &Types) -> String {
fn derive_str(typ: &RocType, types: &Types, include_debug: bool) -> String {
let mut buf = "#[derive(Clone, ".to_string();
if !typ.has_pointer(types) {
if !cannot_derive_copy(typ, types) {
buf.push_str("Copy, ");
}
@ -1303,11 +1309,11 @@ fn derive_str(typ: &RocType, types: &Types, include_debug: bool) -> String {
buf.push_str("Debug, ");
}
if !typ.has_enumeration(types) {
if !cannot_derive_default(typ, types) {
buf.push_str("Default, ");
}
if !typ.has_float(types) {
if !has_float(typ, types) {
buf.push_str("Eq, Ord, Hash, ");
}
@ -1335,13 +1341,13 @@ fn add_nullable_unwrapped(
let discriminant_name = add_discriminant(name, target_info, tag_names, types, impls);
let payload_type = types.get_type(non_null_payload);
let payload_type_name = type_name(non_null_payload, types);
let has_pointer = payload_type.has_pointer(types);
let cannot_derive_copy = cannot_derive_copy(payload_type, types);
// The opaque struct for the tag union
{
// This struct needs its own Clone impl because it has
// a refcount to bump
let derive_extras = if types.get_type(id).has_float(types) {
let derive_extras = if has_float(types.get_type(id), types) {
""
} else {
", Eq, Ord, Hash"
@ -1430,6 +1436,7 @@ pub struct {name} {{
owned_ret_type = answer.owned_ret_type;
borrowed_ret_type = answer.borrowed_ret_type;
}
RocType::Function(_, _) => todo!(),
};
// Add a convenience constructor function for the tag with the payload, e.g.
@ -1481,7 +1488,7 @@ pub struct {name} {{
);
{
let assign_payload = if has_pointer {
let assign_payload = if cannot_derive_copy {
"core::mem::ManuallyDrop::take(&mut *self.pointer)"
} else {
"*self.pointer"
@ -1624,7 +1631,7 @@ pub struct {name} {{
// The Debug impl for the tag union
{
let opt_impl = Some(format!("impl core::fmt::Debug for {name}"));
let extra_deref = if has_pointer { "*" } else { "" };
let extra_deref = if cannot_derive_copy { "*" } else { "" };
let fields_str = match payload_type {
RocType::RocStr
@ -1659,6 +1666,7 @@ pub struct {name} {{
buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}"))
}
RocType::Function(_, _) => todo!(),
};
let body = format!(
@ -1852,3 +1860,124 @@ fn tag_union_struct_help<'a, I: Iterator<Item = &'a (L, TypeId)>, L: Display + P
borrowed_ret_type,
}
}
fn cannot_derive_default(roc_type: &RocType, types: &Types) -> bool {
match roc_type {
RocType::TagUnion { .. } | RocType::RecursivePointer { .. } | RocType::Function(_, _) => {
true
}
RocType::RocStr | RocType::Bool | RocType::Num(_) => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
cannot_derive_default(types.get_type(*id), types)
}
RocType::RocDict(key_id, val_id) => {
cannot_derive_default(types.get_type(*key_id), types)
|| cannot_derive_default(types.get_type(*val_id), types)
}
RocType::Struct { fields, .. } => fields
.iter()
.any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)),
RocType::TagUnionPayload { fields, .. } => fields
.iter()
.any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)),
}
}
/// Useful when determining whether to derive Copy in a Rust type.
fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool {
match roc_type {
RocType::Bool
| RocType::Num(_)
| RocType::TagUnion(RocTagUnion::Enumeration { .. })
| RocType::Function(_, _) => false,
RocType::RocStr
| RocType::RocList(_)
| RocType::RocDict(_, _)
| RocType::RocSet(_)
| RocType::RocBox(_)
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { .. })
| RocType::TagUnion(RocTagUnion::NullableUnwrapped { .. })
| RocType::TagUnion(RocTagUnion::NullableWrapped { .. })
| RocType::TagUnion(RocTagUnion::Recursive { .. })
| RocType::RecursivePointer { .. } => true,
RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => {
tags.iter().any(|(_, payloads)| {
payloads
.iter()
.any(|id| cannot_derive_copy(types.get_type(*id), types))
})
}
RocType::Struct { fields, .. } => fields
.iter()
.any(|(_, type_id)| cannot_derive_copy(types.get_type(*type_id), types)),
RocType::TagUnionPayload { fields, .. } => fields
.iter()
.any(|(_, type_id)| cannot_derive_copy(types.get_type(*type_id), types)),
}
}
/// Useful when determining whether to derive Eq, Ord, and Hash in a Rust type.
fn has_float(roc_type: &RocType, types: &Types) -> bool {
has_float_help(roc_type, types, &[])
}
fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId]) -> bool {
match roc_type {
RocType::Num(num) => {
use RocNum::*;
match num {
F32 | F64 | F128 => true,
I8 | U8 | I16 | U16 | I32 | U32 | I64 | U64 | I128 | U128 | Dec => false,
}
}
RocType::RocStr
| RocType::Bool
| RocType::TagUnion(RocTagUnion::Enumeration { .. })
| RocType::Function(_, _) => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
has_float_help(types.get_type(*id), types, do_not_recurse)
}
RocType::RocDict(key_id, val_id) => {
has_float_help(types.get_type(*key_id), types, do_not_recurse)
|| has_float_help(types.get_type(*val_id), types, do_not_recurse)
}
RocType::Struct { fields, .. } => fields
.iter()
.any(|(_, type_id)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
RocType::TagUnionPayload { fields, .. } => fields
.iter()
.any(|(_, type_id)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
RocType::TagUnion(RocTagUnion::Recursive { tags, .. })
| RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => {
tags.iter().any(|(_, payloads)| {
payloads
.iter()
.any(|id| has_float_help(types.get_type(*id), types, do_not_recurse))
})
}
RocType::TagUnion(RocTagUnion::NullableWrapped { non_null_tags, .. }) => {
non_null_tags.iter().any(|(_, _, payloads)| {
payloads
.iter()
.any(|id| has_float_help(types.get_type(*id), types, do_not_recurse))
})
}
RocType::TagUnion(RocTagUnion::NullableUnwrapped {
non_null_payload: content,
..
})
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { content, .. })
| RocType::RecursivePointer(content) => {
if do_not_recurse.contains(content) {
false
} else {
let mut do_not_recurse: Vec<TypeId> = do_not_recurse.into();
do_not_recurse.push(*content);
has_float_help(types.get_type(*content), types, &do_not_recurse)
}
}
}
}

View file

@ -156,6 +156,7 @@ pub enum RocType {
/// this would be the field of Cons containing the (recursive) StrConsList type,
/// and the TypeId is the TypeId of StrConsList itself.
RecursivePointer(TypeId),
Function(Vec<TypeId>, TypeId),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
@ -202,136 +203,6 @@ impl RocNum {
}
}
impl RocType {
/// Useful when determining whether to derive Copy in a Rust type.
pub fn has_pointer(&self, types: &Types) -> bool {
match self {
RocType::Bool
| RocType::Num(_)
| RocType::TagUnion(RocTagUnion::Enumeration { .. }) => false,
RocType::RocStr
| RocType::RocList(_)
| RocType::RocDict(_, _)
| RocType::RocSet(_)
| RocType::RocBox(_)
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { .. })
| RocType::TagUnion(RocTagUnion::NullableUnwrapped { .. })
| RocType::TagUnion(RocTagUnion::NullableWrapped { .. })
| RocType::TagUnion(RocTagUnion::Recursive { .. })
| RocType::RecursivePointer { .. } => true,
RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => {
tags.iter().any(|(_, payloads)| {
payloads
.iter()
.any(|id| types.get_type(*id).has_pointer(types))
})
}
RocType::Struct { fields, .. } => fields
.iter()
.any(|(_, type_id)| types.get_type(*type_id).has_pointer(types)),
RocType::TagUnionPayload { fields, .. } => fields
.iter()
.any(|(_, type_id)| types.get_type(*type_id).has_pointer(types)),
}
}
/// Useful when determining whether to derive Eq, Ord, and Hash in a Rust type.
pub fn has_float(&self, types: &Types) -> bool {
self.has_float_help(types, &[])
}
fn has_float_help(&self, types: &Types, do_not_recurse: &[TypeId]) -> bool {
match self {
RocType::Num(num) => {
use RocNum::*;
match num {
F32 | F64 | F128 => true,
I8 | U8 | I16 | U16 | I32 | U32 | I64 | U64 | I128 | U128 | Dec => false,
}
}
RocType::RocStr
| RocType::Bool
| RocType::TagUnion(RocTagUnion::Enumeration { .. }) => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
types.get_type(*id).has_float_help(types, do_not_recurse)
}
RocType::RocDict(key_id, val_id) => {
types
.get_type(*key_id)
.has_float_help(types, do_not_recurse)
|| types
.get_type(*val_id)
.has_float_help(types, do_not_recurse)
}
RocType::Struct { fields, .. } => fields.iter().any(|(_, type_id)| {
types
.get_type(*type_id)
.has_float_help(types, do_not_recurse)
}),
RocType::TagUnionPayload { fields, .. } => fields.iter().any(|(_, type_id)| {
types
.get_type(*type_id)
.has_float_help(types, do_not_recurse)
}),
RocType::TagUnion(RocTagUnion::Recursive { tags, .. })
| RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => {
tags.iter().any(|(_, payloads)| {
payloads
.iter()
.any(|id| types.get_type(*id).has_float_help(types, do_not_recurse))
})
}
RocType::TagUnion(RocTagUnion::NullableWrapped { non_null_tags, .. }) => {
non_null_tags.iter().any(|(_, _, payloads)| {
payloads
.iter()
.any(|id| types.get_type(*id).has_float_help(types, do_not_recurse))
})
}
RocType::TagUnion(RocTagUnion::NullableUnwrapped {
non_null_payload: content,
..
})
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { content, .. })
| RocType::RecursivePointer(content) => {
if do_not_recurse.contains(content) {
false
} else {
let mut do_not_recurse: Vec<TypeId> = do_not_recurse.into();
do_not_recurse.push(*content);
types
.get_type(*content)
.has_float_help(types, &do_not_recurse)
}
}
}
}
/// Useful when determining whether to derive Default in a Rust type.
pub fn has_enumeration(&self, types: &Types) -> bool {
match self {
RocType::TagUnion { .. } | RocType::RecursivePointer { .. } => true,
RocType::RocStr | RocType::Bool | RocType::Num(_) => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
types.get_type(*id).has_enumeration(types)
}
RocType::RocDict(key_id, val_id) => {
types.get_type(*key_id).has_enumeration(types)
|| types.get_type(*val_id).has_enumeration(types)
}
RocType::Struct { fields, .. } => fields
.iter()
.any(|(_, type_id)| types.get_type(*type_id).has_enumeration(types)),
RocType::TagUnionPayload { fields, .. } => fields
.iter()
.any(|(_, type_id)| types.get_type(*type_id).has_enumeration(types)),
}
}
}
impl From<IntWidth> for RocNum {
fn from(width: IntWidth) -> Self {
match width {
@ -558,8 +429,38 @@ fn add_type_help<'a>(
}
}
},
Content::Structure(FlatType::Func(_, _, _)) => {
todo!()
Content::Structure(FlatType::Func(args, _closure_var, ret_var)) => {
let args = env.subs.get_subs_slice(*args);
let mut arg_type_ids = Vec::with_capacity(args.len());
for arg_var in args {
let arg_layout = env
.layout_cache
.from_var(env.arena, *arg_var, env.subs)
.expect("Something weird ended up in the content");
arg_type_ids.push(add_type_help(env, arg_layout, *arg_var, None, types));
}
let ret_type_id = {
let ret_layout = env
.layout_cache
.from_var(env.arena, *ret_var, env.subs)
.expect("Something weird ended up in the content");
add_type_help(env, ret_layout, *ret_var, None, types)
};
let fn_type_id =
types.add(RocType::Function(arg_type_ids.clone(), ret_type_id), layout);
types.depends(fn_type_id, ret_type_id);
for arg_type_id in arg_type_ids {
types.depends(fn_type_id, arg_type_id);
}
fn_type_id
}
Content::Structure(FlatType::FunctionOrTagUnion(_, _, _)) => {
todo!()
@ -638,30 +539,6 @@ fn add_builtin_type<'a>(
(Builtin::Decimal, _) => types.add(RocType::Num(RocNum::Dec), layout),
(Builtin::Bool, _) => types.add(RocType::Bool, layout),
(Builtin::Str, _) => types.add(RocType::RocStr, layout),
(Builtin::Dict(key_layout, val_layout), Structure(Apply(Symbol::DICT_DICT, args))) => {
let args = env.subs.get_subs_slice(*args);
debug_assert_eq!(args.len(), 2);
let key_id = add_type_help(env, *key_layout, args[0], opt_name, types);
let val_id = add_type_help(env, *val_layout, args[1], opt_name, types);
let dict_id = types.add(RocType::RocDict(key_id, val_id), layout);
types.depends(dict_id, key_id);
types.depends(dict_id, val_id);
dict_id
}
(Builtin::Set(elem_layout), Structure(Apply(Symbol::SET_SET, args))) => {
let args = env.subs.get_subs_slice(*args);
debug_assert_eq!(args.len(), 1);
let elem_id = add_type_help(env, *elem_layout, args[0], opt_name, types);
let set_id = types.add(RocType::RocSet(elem_id), layout);
types.depends(set_id, elem_id);
set_id
}
(Builtin::List(elem_layout), Structure(Apply(Symbol::LIST_LIST, args))) => {
let args = env.subs.get_subs_slice(*args);
debug_assert_eq!(args.len(), 1);

View file

@ -58,6 +58,7 @@ roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true }
roc_repl_expect = { path = "../repl_expect" }
clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] }
const_format = { version = "0.2.23", features = ["const_generics"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
@ -73,6 +74,7 @@ wasmer-wasi = { version = "2.2.1", optional = true }
libloading = "0.7.1"
roc_gen_llvm = {path = "../compiler/gen_llvm"}
inkwell = {path = "../vendor/inkwell"}
signal-hook = "0.3.14"
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dependencies]

View file

@ -4,7 +4,9 @@ use roc_build::{
program::{self, Problems},
};
use roc_builtins::bitcode;
use roc_load::{LoadingProblem, Threading};
use roc_collections::VecMap;
use roc_load::{Expectations, LoadingProblem, Threading};
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo;
@ -29,6 +31,8 @@ pub struct BuiltFile {
pub binary_path: PathBuf,
pub problems: Problems,
pub total_time: Duration,
pub expectations: VecMap<ModuleId, Expectations>,
pub interns: Interns,
}
#[allow(clippy::too_many_arguments)]
@ -199,8 +203,11 @@ pub fn build_file<'a>(
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
let problems = program::report_problems_monomorphized(&mut loaded);
let expectations = std::mem::take(&mut loaded.expectations);
let loaded = loaded;
let interns = loaded.interns.clone();
enum HostRebuildTiming {
BeforeApp(u128),
ConcurrentWithApp(JoinHandle<u128>),
@ -280,13 +287,7 @@ pub fn build_file<'a>(
let link_start = SystemTime::now();
let problems = match (linking_strategy, link_type) {
(LinkingStrategy::Surgical, _) => {
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
.map_err(|err| {
todo!(
"gracefully handle failing to surgically link with error: {:?}",
err
);
})?;
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path);
problems
}
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
@ -344,6 +345,8 @@ pub fn build_file<'a>(
binary_path,
problems,
total_time,
interns,
expectations,
})
}
@ -390,8 +393,7 @@ fn spawn_rebuild_thread(
exported_symbols,
exported_closure_types,
target_valgrind,
)
.unwrap();
);
}
LinkingStrategy::Legacy => {
rebuild_host(

View file

@ -5,15 +5,19 @@ use build::BuiltFile;
use bumpalo::Bump;
use clap::{Arg, ArgMatches, Command, ValueSource};
use roc_build::link::{LinkType, LinkingStrategy};
use roc_collections::VecMap;
use roc_error_macros::{internal_error, user_error};
use roc_load::{LoadingProblem, Threading};
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{Expectations, LoadingProblem, Threading};
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel;
use roc_region::all::Region;
use roc_repl_cli::expect_mono_module_to_dylib;
use roc_target::TargetInfo;
use std::env;
use std::ffi::{CString, OsStr};
use std::io;
use std::os::raw::c_char;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::process;
use target_lexicon::BinaryFormat;
@ -281,6 +285,8 @@ pub enum FormatMode {
CheckOnly,
}
const SHM_SIZE: i64 = 1024;
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let arena = Bump::new();
let filename = matches.value_of_os(ROC_FILE).unwrap();
@ -341,7 +347,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let shared_fd =
libc::shm_open(cstring.as_ptr().cast(), libc::O_RDWR | libc::O_CREAT, 0o666);
libc::ftruncate(shared_fd, 1024);
libc::ftruncate(shared_fd, SHM_SIZE);
let _shared_ptr = libc::mmap(
std::ptr::null_mut(),
@ -374,34 +380,70 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
)
.unwrap();
let (lib, expects) =
expect_mono_module_to_dylib(arena, target.clone(), loaded, opt_level).unwrap();
let mut loaded = loaded;
let mut expectations = std::mem::take(&mut loaded.expectations);
let loaded = loaded;
let interns = loaded.interns.clone();
let (lib, expects) = expect_mono_module_to_dylib(
arena,
target.clone(),
loaded,
opt_level,
LlvmBackendMode::CliTest,
)
.unwrap();
let name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes!
let cstring = CString::new(name).unwrap();
let arena = &bumpalo::Bump::new();
let interns = arena.alloc(interns);
use roc_gen_llvm::run_jit_function;
let mut failures = 0;
let passed = expects.len();
let mut failed = 0;
let mut passed = 0;
unsafe {
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), libc::O_RDWR, 0o666);
libc::ftruncate(shared_fd, SHM_SIZE);
let shared_ptr = libc::mmap(
std::ptr::null_mut(),
SHM_SIZE as usize,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
shared_fd,
0,
);
for expect in expects {
print!("test {expect}... ");
let result = run_jit_function!(lib, expect, bool, |v: bool| v);
libc::memset(shared_ptr.cast(), 0, SHM_SIZE as _);
match result {
true => println!("ok"),
false => {
failures += 1;
println!("failed")
}
}
}
run_jit_function!(lib, expect, (), |v: ()| v);
let shared_memory_ptr: *const u8 = shared_ptr.cast();
let buffer = std::slice::from_raw_parts(shared_memory_ptr, SHM_SIZE as _);
if buffer.iter().any(|b| *b != 0) {
failed += 1;
render_expect_failure(arena, &mut expectations, interns, shared_memory_ptr);
println!();
} else {
passed += 1;
}
}
}
if failures > 0 {
println!("test result: failed. {passed} passed; {failures} failed;");
if failed > 0 {
println!("test result: failed. {passed} passed; {failed} failed;");
Ok(1)
} else {
println!("test result: ok. {passed} passed; {failures} failed;");
println!("test result: ok. {passed} passed; {failed} failed;");
Ok(0)
}
}
@ -446,7 +488,7 @@ pub fn build(
let linking_strategy = if wasm_dev_backend {
LinkingStrategy::Additive
} else if !roc_linker::supported(&link_type, &triple)
} else if !roc_linker::supported(link_type, &triple)
|| matches.value_of(FLAG_LINKER) == Some("legacy")
{
LinkingStrategy::Legacy
@ -511,6 +553,8 @@ pub fn build(
binary_path,
problems,
total_time,
expectations,
interns,
}) => {
match config {
BuildOnly => {
@ -589,7 +633,15 @@ pub fn build(
let mut bytes = std::fs::read(&binary_path).unwrap();
let x = roc_run(arena, opt_level, triple, args, &mut bytes);
let x = roc_run(
arena,
opt_level,
triple,
args,
&mut bytes,
expectations,
interns,
);
std::mem::forget(bytes);
x
}
@ -613,7 +665,15 @@ pub fn build(
let mut bytes = std::fs::read(&binary_path).unwrap();
let x = roc_run(arena, opt_level, triple, args, &mut bytes);
let x = roc_run(
arena,
opt_level,
triple,
args,
&mut bytes,
expectations,
interns,
);
std::mem::forget(bytes);
x
} else {
@ -667,6 +727,8 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
triple: Triple,
args: I,
binary_bytes: &mut [u8],
expectations: VecMap<ModuleId, Expectations>,
interns: Interns,
) -> io::Result<i32> {
match triple.architecture {
Architecture::Wasm32 => {
@ -701,23 +763,21 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
Ok(0)
}
_ => roc_run_native(arena, opt_level, args, binary_bytes),
_ => roc_run_native(arena, opt_level, args, binary_bytes, expectations, interns),
}
}
/// Run on the native OS (not on wasm)
#[cfg(target_family = "unix")]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: Bump,
opt_level: OptLevel,
fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: &'a Bump,
executable: &ExecutableFile,
args: I,
binary_bytes: &mut [u8],
) -> std::io::Result<i32> {
) -> (
bumpalo::collections::Vec<'a, CString>,
bumpalo::collections::Vec<'a, CString>,
) {
use bumpalo::collections::CollectIn;
use std::os::unix::ffi::OsStrExt;
unsafe {
let executable = roc_run_executable_file_path(binary_bytes)?;
let path = executable.as_path();
let path_cstring = CString::new(path.as_os_str().as_bytes()).unwrap();
@ -726,19 +786,12 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
// strings (i.e., argv[0]) should contain the filename associated
// with the file being executed. The argv array must be terminated
// by a NULL pointer. (Thus, in the new program, argv[argc] will be NULL.)
let c_strings: bumpalo::collections::Vec<CString> = args
let it = args
.into_iter()
.map(|x| CString::new(x.as_ref().as_bytes()).unwrap())
.collect_in(&arena);
.map(|x| CString::new(x.as_ref().as_bytes()).unwrap());
let c_string_pointers = c_strings
.iter()
.map(|x| x.as_bytes_with_nul().as_ptr().cast());
let argv: bumpalo::collections::Vec<*const c_char> = std::iter::once(path_cstring.as_ptr())
.chain(c_string_pointers)
.chain([std::ptr::null()])
.collect_in(&arena);
let argv_cstrings: bumpalo::collections::Vec<CString> =
std::iter::once(path_cstring).chain(it).collect_in(arena);
// envp is an array of pointers to strings, conventionally of the
// form key=value, which are passed as the environment of the new
@ -750,6 +803,31 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
CString::new(v.as_bytes()).unwrap(),
]
})
.collect_in(arena);
(argv_cstrings, envp_cstrings)
}
/// Run on the native OS (not on wasm)
#[cfg(target_family = "unix")]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: Bump,
opt_level: OptLevel,
args: I,
binary_bytes: &mut [u8],
expectations: VecMap<ModuleId, Expectations>,
interns: Interns,
) -> std::io::Result<i32> {
use bumpalo::collections::CollectIn;
unsafe {
let executable = roc_run_executable_file_path(binary_bytes)?;
let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args);
let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
@ -760,8 +838,7 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
match opt_level {
OptLevel::Development => {
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
todo!()
roc_run_native_debug(executable, &argv, &envp, expectations, interns)
}
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
roc_run_native_fast(executable, &argv, &envp);
@ -777,34 +854,16 @@ unsafe fn roc_run_native_fast(
argv: &[*const c_char],
envp: &[*const c_char],
) {
match executable {
#[cfg(target_os = "linux")]
ExecutableFile::MemFd(fd, path) => {
if libc::fexecve(fd, argv.as_ptr(), envp.as_ptr()) != 0 {
if executable.execve(argv, envp) != 0 {
internal_error!(
"libc::fexecve({:?}, ..., ...) failed: {:?}",
path,
"libc::{}({:?}, ..., ...) failed: {:?}",
ExecutableFile::SYSCALL,
executable.as_path(),
errno::errno()
);
}
}
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
ExecutableFile::OnDisk(_, path) => {
use std::os::unix::ffi::OsStrExt;
let path_cstring = CString::new(path.as_os_str().as_bytes()).unwrap();
if libc::execve(path_cstring.as_ptr().cast(), argv.as_ptr(), envp.as_ptr()) != 0 {
internal_error!(
"libc::execve({:?}, ..., ...) failed: {:?}",
path,
errno::errno()
);
}
}
}
}
#[derive(Debug)]
enum ExecutableFile {
#[cfg(target_os = "linux")]
@ -814,6 +873,12 @@ enum ExecutableFile {
}
impl ExecutableFile {
#[cfg(target_os = "linux")]
const SYSCALL: &'static str = "fexecve";
#[cfg(not(target_os = "linux"))]
const SYSCALL: &'static str = "execve";
fn as_path(&self) -> &Path {
match self {
#[cfg(target_os = "linux")]
@ -822,6 +887,222 @@ impl ExecutableFile {
ExecutableFile::OnDisk(_, path_buf) => path_buf.as_ref(),
}
}
unsafe fn execve(&self, argv: &[*const c_char], envp: &[*const c_char]) -> c_int {
match self {
#[cfg(target_os = "linux")]
ExecutableFile::MemFd(fd, _path) => libc::fexecve(*fd, argv.as_ptr(), envp.as_ptr()),
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
ExecutableFile::OnDisk(_, path) => {
use std::os::unix::ffi::OsStrExt;
let path_cstring = CString::new(path.as_os_str().as_bytes()).unwrap();
libc::execve(path_cstring.as_ptr().cast(), argv.as_ptr(), envp.as_ptr())
}
}
}
}
// with Expect
unsafe fn roc_run_native_debug(
executable: ExecutableFile,
argv: &[*const c_char],
envp: &[*const c_char],
mut expectations: VecMap<ModuleId, Expectations>,
interns: Interns,
) {
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
match libc::fork() {
0 => {
// we are the child
if executable.execve(argv, envp) < 0 {
// Get the current value of errno
let e = errno::errno();
// Extract the error code as an i32
let code = e.0;
// Display a human-friendly error message
println!("💥 Error {}: {}", code, e);
}
}
-1 => {
// something failed
// Get the current value of errno
let e = errno::errno();
// Extract the error code as an i32
let code = e.0;
// Display a human-friendly error message
println!("Error {}: {}", code, e);
process::exit(1)
}
1.. => {
let name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes!
let cstring = CString::new(name).unwrap();
let arena = &bumpalo::Bump::new();
let interns = arena.alloc(interns);
for sig in &mut signals {
match sig {
SIGCHLD => {
// clean up
libc::shm_unlink(cstring.as_ptr().cast());
// done!
process::exit(0);
}
SIGUSR1 => {
// this is the signal we use for an expect failure. Let's see what the child told us
let shared_fd =
libc::shm_open(cstring.as_ptr().cast(), libc::O_RDONLY, 0o666);
libc::ftruncate(shared_fd, SHM_SIZE);
let shared_ptr = libc::mmap(
std::ptr::null_mut(),
SHM_SIZE as usize,
libc::PROT_READ,
libc::MAP_SHARED,
shared_fd,
0,
);
let shared_memory_ptr: *const u8 = shared_ptr.cast();
render_expect_failure(arena, &mut expectations, interns, shared_memory_ptr);
}
_ => println!("received signal {}", sig),
}
}
}
_ => unreachable!(),
}
}
fn render_expect_failure<'a>(
arena: &'a Bump,
expectations: &mut VecMap<ModuleId, Expectations>,
interns: &'a Interns,
start: *const u8,
) {
use roc_reporting::report::Report;
use roc_reporting::report::RocDocAllocator;
use ven_pretty::DocAllocator;
// we always run programs as the host
let target_info = (&target_lexicon::Triple::host()).into();
let region_bytes: [u8; 8] = unsafe { *(start.cast()) };
let region: Region = unsafe { std::mem::transmute(region_bytes) };
let module_id_bytes: [u8; 4] = unsafe { *(start.add(8).cast()) };
let module_id: ModuleId = unsafe { std::mem::transmute(module_id_bytes) };
let data = expectations.get_mut(&module_id).unwrap();
let current = data.expectations.get(&region).unwrap();
let subs = arena.alloc(&mut data.subs);
// TODO cache these line offsets?
let path = &data.path;
let filename = data.path.to_owned();
let file_string = std::fs::read_to_string(path).unwrap();
let src_lines: Vec<_> = file_string.lines().collect();
let line_info = roc_region::all::LineInfo::new(&file_string);
let line_col_region = line_info.convert_region(region);
let alloc = RocDocAllocator::new(&src_lines, module_id, interns);
// 8 bytes for region, 4 for module id
let start_offset = 12;
let (symbols, variables): (Vec<_>, Vec<_>) = current.iter().map(|(a, b)| (*a, *b)).unzip();
let error_types: Vec<_> = variables
.iter()
.map(|variable| {
let (error_type, _) = subs.var_to_error_type(*variable);
error_type
})
.collect();
let expressions = roc_repl_expect::get_values(
target_info,
arena,
subs,
interns,
start,
start_offset,
&variables,
)
.unwrap();
use roc_fmt::annotation::Formattable;
use roc_reporting::error::r#type::error_type_to_doc;
let it =
symbols
.iter()
.zip(expressions)
.zip(error_types)
.map(|((symbol, expr), error_type)| {
let mut buf = roc_fmt::Buf::new_in(arena);
expr.format(&mut buf, 0);
alloc.vcat([
alloc
.symbol_unqualified(*symbol)
.append(" : ")
.append(error_type_to_doc(&alloc, error_type)),
alloc
.symbol_unqualified(*symbol)
.append(" = ")
.append(buf.into_bump_str()),
])
});
let doc = if it.len() > 0 {
alloc.stack([
alloc.text("This expectation failed:"),
alloc.region(line_col_region),
alloc.text("The variables used in this expression are:"),
alloc.stack(it),
])
} else {
alloc.stack([
alloc.text("This expectation failed:"),
alloc.region(line_col_region),
alloc.text("I did not record any variables in this expression."),
])
};
let report = Report {
title: "EXPECT FAILED".into(),
doc,
filename,
severity: roc_reporting::report::Severity::RuntimeError,
};
let mut buf = String::new();
report.render(
roc_reporting::report::RenderTarget::ColorTerminal,
&mut buf,
&alloc,
&roc_reporting::report::DEFAULT_PALETTE,
);
println!("{}", buf);
}
#[cfg(target_os = "linux")]
@ -878,6 +1159,8 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
_arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
_args: I,
_binary_bytes: &mut [u8],
_expectations: VecMap<ModuleId, Expectations>,
_interns: Interns,
) -> io::Result<i32> {
todo!("TODO support running roc programs on non-UNIX targets");
// let mut cmd = std::process::Command::new(&binary_path);

View file

@ -145,6 +145,8 @@ mod cli_run {
vec.push(VALGRIND_FLAG);
}
vec.push("--max-threads=1");
vec.into_iter()
};

View file

@ -733,31 +733,6 @@ fn call_spec(
}
match op {
DictWalk { xs, state } => {
let dict = env.symbols[xs];
let state = env.symbols[state];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let key = builder.add_get_tuple_field(block, element, 0)?;
let val = builder.add_get_tuple_field(block, element, 1)?;
let new_state = call_function!(builder, block, [state, key, val]);
Ok(new_state)
};
let state_layout = argument_layouts[0];
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap { xs } => {
let list = env.symbols[xs];
@ -1001,7 +976,7 @@ fn lowlevel_spec(
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
ListLen | DictSize => {
ListLen => {
// TODO should this touch the heap cell?
// just dream up a unit value
builder.add_make_tuple(block, &[])
@ -1087,47 +1062,6 @@ fn lowlevel_spec(
builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code])
}
DictEmpty => match layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?;
let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?;
new_dict(builder, block, key_id, value_id)
}
_ => unreachable!("empty array does not have a list layout"),
},
DictGetUnsafe => {
// NOTE DictGetUnsafe returns a { flag: Bool, value: v }
// when the flag is True, the value is found and defined;
// otherwise it is not and `Dict.get` should return `Err ...`
let dict = env.symbols[&arguments[0]];
let key = env.symbols[&arguments[1]];
// indicate that we use the key
builder.add_recursive_touch(block, key)?;
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let _unit = builder.add_touch(block, cell)?;
builder.add_bag_get(block, bag)
}
DictInsert => {
let dict = env.symbols[&arguments[0]];
let key = env.symbols[&arguments[1]];
let value = env.symbols[&arguments[2]];
let key_value = builder.add_make_tuple(block, &[key, value])?;
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
builder.add_bag_insert(block, bag, key_value)?;
with_new_heap_cell(builder, block, bag)
}
_other => {
// println!("missing {:?}", _other);
// TODO overly pessimstic
@ -1515,24 +1449,6 @@ fn builtin_spec(
Int(_) | Bool => builder.add_tuple_type(&[]),
Decimal | Float(_) => builder.add_tuple_type(&[]),
Str => str_type(builder),
Dict(key_layout, value_layout) => {
let value_type = layout_spec_help(builder, value_layout, when_recursive)?;
let key_type = layout_spec_help(builder, key_layout, when_recursive)?;
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
builder.add_tuple_type(&[cell, bag])
}
Set(key_layout) => {
let value_type = builder.add_tuple_type(&[])?;
let key_type = layout_spec_help(builder, key_layout, when_recursive)?;
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
builder.add_tuple_type(&[cell, bag])
}
List(element_layout) => {
let element_type = layout_spec_help(builder, element_layout, when_recursive)?;
@ -1560,9 +1476,6 @@ fn static_list_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
const LIST_CELL_INDEX: u32 = 0;
const LIST_BAG_INDEX: u32 = 1;
const DICT_CELL_INDEX: u32 = LIST_CELL_INDEX;
const DICT_BAG_INDEX: u32 = LIST_BAG_INDEX;
#[allow(dead_code)]
const BOX_CELL_INDEX: u32 = LIST_CELL_INDEX;
const BOX_VALUE_INDEX: u32 = LIST_BAG_INDEX;
@ -1584,17 +1497,6 @@ fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId)
with_new_heap_cell(builder, block, bag)
}
fn new_dict(
builder: &mut FuncDefBuilder,
block: BlockId,
key_type: TypeId,
value_type: TypeId,
) -> Result<ValueId> {
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
let bag = builder.add_empty_bag(block, element_type)?;
with_new_heap_cell(builder, block, bag)
}
fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
let module = MOD_APP;

View file

@ -1,815 +0,0 @@
const std = @import("std");
const testing = std.testing;
const expectEqual = testing.expectEqual;
const mem = std.mem;
const assert = std.debug.assert;
const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList;
const INITIAL_SEED = 0xc70f6907;
const InPlace = enum(u8) {
InPlace,
Clone,
};
const Slot = enum(u8) {
Empty,
Filled,
PreviouslyFilled,
};
const MaybeIndexTag = enum { index, not_found };
const MaybeIndex = union(MaybeIndexTag) { index: usize, not_found: void };
fn nextSeed(seed: u64) u64 {
// TODO is this a valid way to get a new seed? are there better ways?
return seed + 1;
}
fn totalCapacityAtLevel(input: usize) usize {
if (input == 0) {
return 0;
}
var n = input;
var slots: usize = 8;
while (n > 1) : (n -= 1) {
slots = slots * 2 + slots;
}
return slots;
}
fn capacityOfLevel(input: usize) usize {
if (input == 0) {
return 0;
}
var n = input;
var slots: usize = 8;
while (n > 1) : (n -= 1) {
slots = slots * 2;
}
return slots;
}
// aligmnent of elements. The number (16 or 8) indicates the maximum
// alignment of the key and value. The tag furthermore indicates
// which has the biggest aligmnent. If both are the same, we put
// the key first
const Alignment = extern struct {
bits: u8,
const VALUE_BEFORE_KEY_FLAG: u8 = 0b1000_0000;
fn toU32(self: Alignment) u32 {
if (self.bits >= VALUE_BEFORE_KEY_FLAG) {
return self.bits ^ Alignment.VALUE_BEFORE_KEY_FLAG;
} else {
return self.bits;
}
}
fn keyFirst(self: Alignment) bool {
if (self.bits & Alignment.VALUE_BEFORE_KEY_FLAG > 0) {
return false;
} else {
return true;
}
}
};
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,
alignment: Alignment,
) void {
return utils.decref(bytes_or_null, data_bytes, alignment.toU32());
}
pub fn allocateWithRefcount(
data_bytes: usize,
alignment: Alignment,
) [*]u8 {
return utils.allocateWithRefcount(data_bytes, alignment.toU32());
}
pub const RocDict = extern struct {
dict_bytes: ?[*]u8,
dict_entries_len: usize,
number_of_levels: usize,
pub fn empty() RocDict {
return RocDict{
.dict_entries_len = 0,
.number_of_levels = 0,
.dict_bytes = null,
};
}
pub fn allocate(
number_of_levels: usize,
number_of_entries: usize,
alignment: Alignment,
key_size: usize,
value_size: usize,
) RocDict {
const number_of_slots = totalCapacityAtLevel(number_of_levels);
const slot_size = slotSize(key_size, value_size);
const data_bytes = number_of_slots * slot_size;
return RocDict{
.dict_bytes = allocateWithRefcount(data_bytes, alignment),
.number_of_levels = number_of_levels,
.dict_entries_len = number_of_entries,
};
}
pub fn reallocate(
self: RocDict,
alignment: Alignment,
key_width: usize,
value_width: usize,
) RocDict {
const new_level = self.number_of_levels + 1;
const slot_size = slotSize(key_width, value_width);
const old_capacity = self.capacity();
const new_capacity = totalCapacityAtLevel(new_level);
const delta_capacity = new_capacity - old_capacity;
const data_bytes = new_capacity * slot_size;
const first_slot = allocateWithRefcount(data_bytes, alignment);
// transfer the memory
if (self.dict_bytes) |source_ptr| {
const dest_ptr = first_slot;
var source_offset: usize = 0;
var dest_offset: usize = 0;
if (alignment.keyFirst()) {
// copy keys
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width);
// copy values
source_offset = old_capacity * key_width;
dest_offset = new_capacity * key_width;
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width);
} else {
// copy values
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width);
// copy keys
source_offset = old_capacity * value_width;
dest_offset = new_capacity * value_width;
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width);
}
// copy slots
source_offset = old_capacity * (key_width + value_width);
dest_offset = new_capacity * (key_width + value_width);
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * @sizeOf(Slot));
}
var i: usize = 0;
const first_new_slot_value = first_slot + old_capacity * slot_size + delta_capacity * (key_width + value_width);
while (i < (new_capacity - old_capacity)) : (i += 1) {
(first_new_slot_value)[i] = @enumToInt(Slot.Empty);
}
const result = RocDict{
.dict_bytes = first_slot,
.number_of_levels = self.number_of_levels + 1,
.dict_entries_len = self.dict_entries_len,
};
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
decref(self.dict_bytes, self.capacity() * slotSize(key_width, value_width), alignment);
return result;
}
pub fn asU8ptr(self: RocDict) [*]u8 {
return @ptrCast([*]u8, self.dict_bytes);
}
pub fn len(self: RocDict) usize {
return self.dict_entries_len;
}
pub fn isEmpty(self: RocDict) bool {
return self.len() == 0;
}
pub fn isUnique(self: RocDict) bool {
// the empty dict is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
return true;
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.dict_bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
pub fn capacity(self: RocDict) usize {
return totalCapacityAtLevel(self.number_of_levels);
}
pub fn makeUnique(self: RocDict, alignment: Alignment, key_width: usize, value_width: usize) RocDict {
if (self.isEmpty()) {
return self;
}
if (self.isUnique()) {
return self;
}
// unfortunately, we have to clone
var new_dict = RocDict.allocate(self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_dict.dict_bytes);
const number_of_bytes = self.capacity() * (@sizeOf(Slot) + key_width + value_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.capacity() * slotSize(key_width, value_width);
decref(self.dict_bytes, data_bytes, alignment);
return new_dict;
}
fn getSlot(self: *const RocDict, index: usize, key_width: usize, value_width: usize) Slot {
const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot);
const ptr = self.dict_bytes orelse unreachable;
return @intToEnum(Slot, ptr[offset]);
}
fn setSlot(self: *RocDict, index: usize, key_width: usize, value_width: usize, slot: Slot) void {
const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot);
const ptr = self.dict_bytes orelse unreachable;
ptr[offset] = @enumToInt(slot);
}
fn setKey(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (key_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
} else {
break :blk (self.capacity() * value_width) + (index * key_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
const source = data orelse unreachable;
const dest = ptr + offset;
@memcpy(dest, source, key_width);
}
fn getKey(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (key_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
} else {
break :blk (self.capacity() * value_width) + (index * key_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
return ptr + offset;
}
fn setValue(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (value_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
} else {
break :blk (index * value_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
const source = data orelse unreachable;
const dest = ptr + offset;
@memcpy(dest, source, value_width);
}
fn getValue(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (value_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
} else {
break :blk (index * value_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
return ptr + offset;
}
fn findIndex(self: *const RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) MaybeIndex {
if (self.isEmpty()) {
return MaybeIndex.not_found;
}
var seed: u64 = INITIAL_SEED;
var current_level: usize = 1;
var current_level_size: usize = 8;
var next_level_size: usize = 2 * current_level_size;
while (true) {
if (current_level > self.number_of_levels) {
return MaybeIndex.not_found;
}
// hash the key, and modulo by the maximum size
// (so we get an in-bounds index)
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + @intCast(usize, (hash % current_level_size));
switch (self.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => {
return MaybeIndex.not_found;
},
Slot.Filled => {
// is this the same key, or a new key?
const current_key = self.getKey(index, alignment, key_width, value_width);
if (is_eq(key, current_key)) {
return MaybeIndex{ .index = index };
} else {
current_level += 1;
current_level_size *= 2;
next_level_size *= 2;
seed = nextSeed(seed);
continue;
}
},
}
}
}
};
// Dict.empty
pub fn dictEmpty(dict: *RocDict) callconv(.C) void {
dict.* = RocDict.empty();
}
pub fn slotSize(key_size: usize, value_size: usize) usize {
return @sizeOf(Slot) + key_size + value_size;
}
// Dict.len
pub fn dictLen(dict: RocDict) callconv(.C) usize {
return dict.dict_entries_len;
}
// commonly used type aliases
const Opaque = ?[*]u8;
const HashFn = fn (u64, ?[*]u8) callconv(.C) u64;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Inc = fn (?[*]u8) callconv(.C) void;
const IncN = fn (?[*]u8, usize) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
// Dict.insert : Dict k v, k, v -> Dict k v
pub fn dictInsert(
input: RocDict,
alignment: Alignment,
key: Opaque,
key_width: usize,
value: Opaque,
value_width: usize,
hash_fn: HashFn,
is_eq: EqFn,
dec_key: Dec,
dec_value: Dec,
output: *RocDict,
) callconv(.C) void {
var seed: u64 = INITIAL_SEED;
var result = input.makeUnique(alignment, key_width, value_width);
var current_level: usize = 1;
var current_level_size: usize = 8;
var next_level_size: usize = 2 * current_level_size;
while (true) {
if (current_level > result.number_of_levels) {
result = result.reallocate(alignment, key_width, value_width);
}
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + @intCast(usize, (hash % current_level_size));
assert(index < result.capacity());
switch (result.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => {
result.setSlot(index, key_width, value_width, Slot.Filled);
result.setKey(index, alignment, key_width, value_width, key);
result.setValue(index, alignment, key_width, value_width, value);
result.dict_entries_len += 1;
break;
},
Slot.Filled => {
// is this the same key, or a new key?
const current_key = result.getKey(index, alignment, key_width, value_width);
if (is_eq(key, current_key)) {
// we will override the old value, but first have to decrement its refcount
const current_value = result.getValue(index, alignment, key_width, value_width);
dec_value(current_value);
// we must consume the key argument!
dec_key(key);
result.setValue(index, alignment, key_width, value_width, value);
break;
} else {
seed = nextSeed(seed);
current_level += 1;
current_level_size *= 2;
next_level_size *= 2;
continue;
}
},
}
}
// write result into pointer
output.* = result;
}
// Dict.remove : Dict k v, k -> Dict k v
pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
switch (input.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
// the key was not found; we're done
output.* = input;
return;
},
MaybeIndex.index => |index| {
var dict = input.makeUnique(alignment, key_width, value_width);
assert(index < dict.capacity());
dict.setSlot(index, key_width, value_width, Slot.PreviouslyFilled);
const old_key = dict.getKey(index, alignment, key_width, value_width);
const old_value = dict.getValue(index, alignment, key_width, value_width);
dec_key(old_key);
dec_value(old_value);
dict.dict_entries_len -= 1;
// if the dict is now completely empty, free its allocation
if (dict.dict_entries_len == 0) {
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(dict.dict_bytes, data_bytes, alignment);
output.* = RocDict.empty();
return;
}
output.* = dict;
},
}
}
// Dict.contains : Dict k v, k -> Bool
pub fn dictContains(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) callconv(.C) bool {
switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
return false;
},
MaybeIndex.index => |_| {
return true;
},
}
}
// Dict.get : Dict k v, k -> { flag: bool, value: Opaque }
pub fn dictGet(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_value: Inc) callconv(.C) extern struct { value: Opaque, flag: bool } {
switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
return .{ .flag = false, .value = null };
},
MaybeIndex.index => |index| {
var value = dict.getValue(index, alignment, key_width, value_width);
inc_value(value);
return .{ .flag = true, .value = value };
},
}
}
// Dict.elementsRc
// increment or decrement all dict elements (but not the dict's allocation itself)
pub fn elementsRc(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, modify_key: Inc, modify_value: Inc) callconv(.C) void {
const size = dict.capacity();
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
modify_key(dict.getKey(i, alignment, key_width, value_width));
modify_value(dict.getValue(i, alignment, key_width, value_width));
},
else => {},
}
}
}
pub fn dictKeys(
dict: RocDict,
alignment: Alignment,
key_width: usize,
value_width: usize,
inc_key: Inc,
) callconv(.C) RocList {
const size = dict.capacity();
var length: usize = 0;
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
length += 1;
},
else => {},
}
}
if (length == 0) {
return RocList.empty();
}
const data_bytes = length * key_width;
var ptr = allocateWithRefcount(data_bytes, alignment);
i = 0;
var copied: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict.getKey(i, alignment, key_width, value_width);
inc_key(key);
const key_cast = @ptrCast([*]const u8, key);
@memcpy(ptr + (copied * key_width), key_cast, key_width);
copied += 1;
},
else => {},
}
}
return RocList{ .bytes = ptr, .length = length, .capacity = length };
}
pub fn dictValues(
dict: RocDict,
alignment: Alignment,
key_width: usize,
value_width: usize,
inc_value: Inc,
) callconv(.C) RocList {
const size = dict.capacity();
var length: usize = 0;
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
length += 1;
},
else => {},
}
}
if (length == 0) {
return RocList.empty();
}
const data_bytes = length * value_width;
var ptr = allocateWithRefcount(data_bytes, alignment);
i = 0;
var copied: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const value = dict.getValue(i, alignment, key_width, value_width);
inc_value(value);
const value_cast = @ptrCast([*]const u8, value);
@memcpy(ptr + (copied * value_width), value_cast, value_width);
copied += 1;
},
else => {},
}
}
return RocList{ .bytes = ptr, .length = length, .capacity = length };
}
fn doNothing(_: Opaque) callconv(.C) void {
return;
}
pub fn dictUnion(
dict1: RocDict,
dict2: RocDict,
alignment: Alignment,
key_width: usize,
value_width: usize,
hash_fn: HashFn,
is_eq: EqFn,
inc_key: Inc,
inc_value: Inc,
output: *RocDict,
) callconv(.C) void {
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
while (i < dict2.capacity()) : (i += 1) {
switch (dict2.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict2.getKey(i, alignment, key_width, value_width);
switch (output.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
const value = dict2.getValue(i, alignment, key_width, value_width);
inc_value(value);
// we need an extra RC token for the key
inc_key(key);
inc_value(value);
// we know the newly added key is not a duplicate, so the `dec`s are unreachable
const dec_key = doNothing;
const dec_value = doNothing;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// the key is already in the output dict
continue;
},
}
},
else => {},
}
}
}
pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// keep this key/value
continue;
},
}
},
else => {},
}
}
}
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
// keep this key/value
continue;
},
MaybeIndex.index => |_| {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
}
},
else => {},
}
}
}
pub fn setFromList(list: RocList, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, output: *RocDict) callconv(.C) void {
output.* = RocDict.empty();
var ptr = @ptrCast([*]u8, list.bytes);
const dec_value = doNothing;
const value = null;
const size = list.length;
var i: usize = 0;
while (i < size) : (i += 1) {
const key = ptr + i * key_width;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
}
// NOTE: decref checks for the empty case
const data_bytes = size * key_width;
decref(list.bytes, data_bytes, alignment);
}
pub fn dictWalk(
dict: RocDict,
caller: Caller3,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
accum: Opaque,
alignment: Alignment,
key_width: usize,
value_width: usize,
accum_width: usize,
output: Opaque,
) callconv(.C) void {
const alignment_u32 = alignment.toU32();
// allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea
// TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32) orelse unreachable;
var b1 = output orelse unreachable;
var b2 = bytes_ptr;
if (data_is_owned) {
inc_n_data(data, dict.len());
}
@memcpy(b2, accum orelse unreachable, accum_width);
var i: usize = 0;
const size = dict.capacity();
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict.getKey(i, alignment, key_width, value_width);
const value = dict.getValue(i, alignment, key_width, value_width);
caller(data, b2, key, value, b1);
std.mem.swap([*]u8, &b1, &b2);
},
else => {},
}
}
@memcpy(output orelse unreachable, b2, accum_width);
utils.dealloc(bytes_ptr, alignment_u32);
}

View file

@ -0,0 +1,39 @@
const std = @import("std");
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn getppid() c_int;
const SIGUSR1: c_int = 10;
const O_RDWR: c_int = 2;
const O_CREAT: c_int = 64;
pub const PROT_WRITE: c_int = 2;
pub const MAP_SHARED: c_int = 0x0001;
pub fn expectFailedStart() callconv(.C) [*]u8 {
const name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes!
const shared_fd = shm_open(@ptrCast(*const i8, name), O_RDWR | O_CREAT, 0o666);
const shared_ptr = mmap(
null,
4096,
PROT_WRITE,
MAP_SHARED,
shared_fd,
0,
);
const ptr = @ptrCast([*]u8, shared_ptr);
return ptr;
}
pub fn expectFailedFinalize() callconv(.C) void {
const parent_pid = getppid();
_ = kill(parent_pid, SIGUSR1);
}

View file

@ -2,6 +2,7 @@ const std = @import("std");
const builtin = @import("builtin");
const math = std.math;
const utils = @import("utils.zig");
const expect = @import("expect.zig");
const ROC_BUILTINS = "roc_builtins";
const NUM = "num";
@ -54,31 +55,6 @@ comptime {
exportListFn(list.listIsUnique, "is_unique");
}
// Dict Module
const dict = @import("dict.zig");
const hash = @import("hash.zig");
comptime {
exportDictFn(dict.dictLen, "len");
exportDictFn(dict.dictEmpty, "empty");
exportDictFn(dict.dictInsert, "insert");
exportDictFn(dict.dictRemove, "remove");
exportDictFn(dict.dictContains, "contains");
exportDictFn(dict.dictGet, "get");
exportDictFn(dict.elementsRc, "elementsRc");
exportDictFn(dict.dictKeys, "keys");
exportDictFn(dict.dictValues, "values");
exportDictFn(dict.dictUnion, "union");
exportDictFn(dict.dictIntersection, "intersection");
exportDictFn(dict.dictDifference, "difference");
exportDictFn(dict.dictWalk, "walk");
exportDictFn(dict.setFromList, "set_from_list");
exportDictFn(hash.wyhash, "hash");
exportDictFn(hash.wyhash_rocstr, "hash_str");
}
// Num Module
const num = @import("num.zig");
@ -189,6 +165,11 @@ comptime {
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
if (builtin.target.cpu.arch != .wasm32) {
exportUtilsFn(expect.expectFailedStart, "expect_failed_start");
exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize");
}
if (builtin.target.cpu.arch == .aarch64) {
@export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
@export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak });

View file

@ -1,6 +1,8 @@
interface Dict
exposes [
Dict,
empty,
withCapacity,
single,
get,
walk,
@ -10,12 +12,14 @@ interface Dict
contains,
keys,
values,
union,
intersection,
difference,
insertAll,
keepShared,
removeAll,
]
imports [
Bool.{ Bool },
Result.{ Result },
List,
]
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
@ -66,34 +70,111 @@ interface Dict
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering.
Dict k v := List [Pair k v]
## An empty dictionary.
empty : Dict k v
empty = @Dict []
withCapacity : Nat -> Dict k v
withCapacity = \n -> @Dict (List.withCapacity n)
get : Dict k v, k -> Result v [KeyNotFound]*
get = \dict, key ->
result = getLowlevel dict key
get = \@Dict list, needle ->
when List.find list (\Pair key _ -> key == needle) is
Ok (Pair _ v) ->
Ok v
when result.flag is
True -> Ok result.value
False -> Err KeyNotFound
getLowlevel : Dict k v, k -> { flag : Bool, value : v }
Err NotFound ->
Err KeyNotFound
walk : Dict k v, state, (state, k, v -> state) -> state
walk = \@Dict list, initialState, transform ->
List.walk list initialState (\state, Pair k v -> transform state k v)
insert : Dict k v, k, v -> Dict k v
insert = \@Dict list, k, v ->
when List.findIndex list (\Pair key _ -> key == k) is
Err NotFound ->
insertFresh (@Dict list) k v
Ok index ->
list
|> List.set index (Pair k v)
|> @Dict
len : Dict k v -> Nat
len = \@Dict list ->
List.len list
remove : Dict k v, k -> Dict k v
remove = \@Dict list, key ->
when List.findIndex list (\Pair k _ -> k == key) is
Err NotFound ->
@Dict list
Ok index ->
lastIndex = List.len list - 1
list
|> List.swap index lastIndex
|> List.dropLast
|> @Dict
contains : Dict k v, k -> Bool
contains = \@Dict list, needle ->
step = \_, Pair key _val ->
if key == needle then
Break {}
else
Continue {}
when List.iterate list {} step is
Continue _ -> False
Break _ -> True
single : k, v -> Dict k v
single = \key, value ->
Dict.empty
|> Dict.insert key value
@Dict [Pair key value]
## Returns a [List] of the dictionary's keys.
keys : Dict k v -> List k
keys = \@Dict list ->
List.map list (\Pair k _ -> k)
## Returns a [List] of the dictionary's values.
## Returns a [List] of the Dict's values
values : Dict k v -> List v
union : Dict k v, Dict k v -> Dict k v
intersection : Dict k v, Dict k v -> Dict k v
difference : Dict k v, Dict k v -> Dict k v
values = \@Dict list ->
List.map list (\Pair _ v -> v)
# union : Dict k v, Dict k v -> Dict k v
insertAll : Dict k v, Dict k v -> Dict k v
insertAll = \xs, @Dict ys ->
List.walk ys xs (\state, Pair k v -> Dict.insertIfVacant state k v)
# intersection : Dict k v, Dict k v -> Dict k v
keepShared : Dict k v, Dict k v -> Dict k v
keepShared = \@Dict xs, ys ->
List.keepIf xs (\Pair k _ -> Dict.contains ys k)
|> @Dict
# difference : Dict k v, Dict k v -> Dict k v
removeAll : Dict k v, Dict k v -> Dict k v
removeAll = \xs, @Dict ys ->
List.walk ys xs (\state, Pair k _ -> Dict.remove state k)
## Internal helper function to insert a new association
##
## Precondition: `k` should not exist in the Dict yet.
insertFresh : Dict k v, k, v -> Dict k v
insertFresh = \@Dict list, k, v ->
list
|> List.append (Pair k v)
|> @Dict
insertIfVacant : Dict k v, k, v -> Dict k v
insertIfVacant = \dict, key, value ->
if Dict.contains dict key then
dict
else
Dict.insert dict key value

View file

@ -825,6 +825,7 @@ sublist = \list, config ->
else
sublistLowlevel list config.start config.len
## low-level slicing operation that does no bounds checking
sublistLowlevel : List elem, Nat, Nat -> List elem
## Intersperses `sep` between the elements of `list`

View file

@ -1,5 +1,6 @@
interface Set
exposes [
Set,
empty,
single,
walk,
@ -13,24 +14,41 @@ interface Set
intersection,
difference,
]
imports [List, Bool.{ Bool }, Dict.{ values }]
imports [List, Bool.{ Bool }, Dict.{ Dict }]
Set k := Dict.Dict k {}
fromDict : Dict k {} -> Set k
fromDict = \dict -> @Set dict
toDict : Set k -> Dict k {}
toDict = \@Set dict -> dict
## An empty set.
empty : Set k
empty = fromDict Dict.empty
single : k -> Set k
single = \key ->
@Set (Dict.single key {})
## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
insert : Set k, k -> Set k
insert = \@Set dict, key ->
dict
|> Dict.insert key {}
|> @Set
len : Set k -> Nat
len = \set ->
set
|> Set.toDict
|> Dict.len
len = \@Set dict ->
Dict.len dict
## Drops the given element from the set.
remove : Set k, k -> Set k
remove = \@Set dict, key ->
@Set (Dict.remove dict key)
contains : Set k, k -> Bool
contains = \set, key ->
@ -38,15 +56,27 @@ contains = \set, key ->
|> Set.toDict
|> Dict.contains key
# toList = \set -> Dict.keys (toDict set)
toList : Set k -> List k
toList = \@Set dict ->
Dict.keys dict
fromList : List k -> Set k
fromList = \list ->
initial = @Set (Dict.withCapacity (List.len list))
List.walk list initial \set, key -> Set.insert set key
union : Set k, Set k -> Set k
intersection : Set k, Set k -> Set k
difference : Set k, Set k -> Set k
union = \@Set dict1, @Set dict2 ->
@Set (Dict.insertAll dict1 dict2)
toDict : Set k -> Dict k {}
intersection : Set k, Set k -> Set k
intersection = \@Set dict1, @Set dict2 ->
@Set (Dict.keepShared dict1 dict2)
difference : Set k, Set k -> Set k
difference = \@Set dict1, @Set dict2 ->
@Set (Dict.removeAll dict1 dict2)
walk : Set k, state, (state, k -> state) -> state
walk = \set, state, step ->

View file

@ -172,11 +172,11 @@ countGraphemes : Str -> Nat
## **Performance Note:** 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.startsWithScalar "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithScalar '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithScalar "🕊"` instead.
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
## This will not work for graphemes which take up multiple code points, however;
## `Str.startsWithScalar '👩‍👩‍👦‍👦'` would be a compiler error because 👩‍👩‍👦‍👦 takes up
## multiple code points and cannot be represented as a single [U32].
## You'd need to use `Str.startsWithScalar "🕊"` instead.
startsWithScalar : Str, U32 -> Bool
## Return a [List] of the [unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)

View file

@ -337,24 +337,6 @@ pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
pub const DICT_LEN: &str = "roc_builtins.dict.len";
pub const DICT_EMPTY: &str = "roc_builtins.dict.empty";
pub const DICT_INSERT: &str = "roc_builtins.dict.insert";
pub const DICT_REMOVE: &str = "roc_builtins.dict.remove";
pub const DICT_CONTAINS: &str = "roc_builtins.dict.contains";
pub const DICT_GET: &str = "roc_builtins.dict.get";
pub const DICT_ELEMENTS_RC: &str = "roc_builtins.dict.elementsRc";
pub const DICT_KEYS: &str = "roc_builtins.dict.keys";
pub const DICT_VALUES: &str = "roc_builtins.dict.values";
pub const DICT_UNION: &str = "roc_builtins.dict.union";
pub const DICT_DIFFERENCE: &str = "roc_builtins.dict.difference";
pub const DICT_INTERSECTION: &str = "roc_builtins.dict.intersection";
pub const DICT_WALK: &str = "roc_builtins.dict.walk";
pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
pub const LIST_MAP3: &str = "roc_builtins.list.map3";
@ -395,6 +377,9 @@ pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
pub const UTILS_EXPECT_FAILED_START: &str = "roc_builtins.utils.expect_failed_start";
pub const UTILS_EXPECT_FAILED_FINALIZE: &str = "roc_builtins.utils.expect_failed_finalize";
pub const UTILS_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp";

View file

@ -359,9 +359,6 @@ impl IAbilitiesStore<Resolved> {
}
pub fn insert_resolved(&mut self, id: SpecializationId, specialization: Symbol) {
// May not be a thing in mono
// debug_assert!(self.is_specialization_name(specialization));
let old_specialization = self.resolved_specializations.insert(id, specialization);
debug_assert!(
@ -371,15 +368,6 @@ impl IAbilitiesStore<Resolved> {
);
}
pub fn remove_resolved(&mut self, id: SpecializationId) {
let old_specialization = self.resolved_specializations.remove(&id);
debug_assert!(
old_specialization.is_some(),
"Trying to remove a resolved specialization that was never there!",
);
}
pub fn get_resolved(&self, id: SpecializationId) -> Option<Symbol> {
self.resolved_specializations.get(&id).copied()
}

View file

@ -58,18 +58,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_DIV_FRAC => Some(lowlevel_2(Symbol::NUM_DIV_FRAC, LowLevel::NumDivUnchecked, var_store)),
Symbol::NUM_DIV_TRUNC => Some(lowlevel_2(Symbol::NUM_DIV_TRUNC, LowLevel::NumDivUnchecked, var_store)),
Symbol::DICT_EMPTY => Some(dict_empty(Symbol::DICT_EMPTY, var_store)),
Symbol::SET_UNION => Some(lowlevel_2(Symbol::SET_UNION, LowLevel::DictUnion, var_store)),
Symbol::SET_DIFFERENCE => Some(lowlevel_2(Symbol::SET_DIFFERENCE, LowLevel::DictDifference, var_store)),
Symbol::SET_INTERSECTION => Some(lowlevel_2(Symbol::SET_INTERSECTION, LowLevel::DictIntersection, var_store)),
Symbol::SET_TO_LIST => Some(lowlevel_1(Symbol::SET_TO_LIST, LowLevel::DictKeys, var_store)),
Symbol::SET_REMOVE => Some(lowlevel_2(Symbol::SET_REMOVE, LowLevel::DictRemove, var_store)),
Symbol::SET_INSERT => Some(set_insert(Symbol::SET_INSERT, var_store)),
Symbol::SET_EMPTY => Some(set_empty(Symbol::SET_EMPTY, var_store)),
Symbol::SET_SINGLE => Some(set_single(Symbol::SET_SINGLE, var_store)),
_ => None,
}
}
@ -94,7 +82,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
LowLevel::NumToIntChecked => unreachable!(),
LowLevel::NumToFloatChecked => unreachable!(),
LowLevel::NumDivUnchecked => unreachable!(),
LowLevel::DictEmpty => unreachable!(),
// these are used internally and not tied to a symbol
LowLevel::Hash => unimplemented!(),
@ -158,23 +145,6 @@ map_symbol_to_lowlevel_and_arity! {
ListSwap; LIST_SWAP; 3,
ListGetCapacity; LIST_CAPACITY; 1,
DictSize; DICT_LEN; 1,
DictInsert; DICT_INSERT; 3,
DictRemove; DICT_REMOVE; 2,
DictContains; DICT_CONTAINS; 2,
DictGetUnsafe; DICT_GET_LOWLEVEL; 2,
DictKeys; DICT_KEYS; 1,
DictValues; DICT_VALUES; 1,
DictUnion; DICT_UNION; 2,
DictIntersection; DICT_INTERSECTION; 2,
DictDifference; DICT_DIFFERENCE; 2,
DictWalk; DICT_WALK; 3,
DictGetCapacity; DICT_CAPACITY; 1,
SetFromList; SET_FROM_LIST; 1,
SetToDict; SET_TO_DICT; 1,
SetGetCapacity; SET_CAPACITY; 1,
NumAdd; NUM_ADD; 2,
NumAddWrap; NUM_ADD_WRAP; 2,
NumAddChecked; NUM_ADD_CHECKED_LOWLEVEL; 2,
@ -567,95 +537,3 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel)
ret_var,
)
}
/// Dict.empty : Dict * *
fn dict_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let dict_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::DictEmpty,
args: vec![],
ret_var: dict_var,
};
Def {
annotation: None,
expr_var: dict_var,
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// Set.empty : Set *
fn set_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let set_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::DictEmpty,
args: vec![],
ret_var: set_var,
};
Def {
annotation: None,
expr_var: set_var,
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// Set.insert : Set k, k -> Set k
fn set_insert(symbol: Symbol, var_store: &mut VarStore) -> Def {
let dict_var = var_store.fresh();
let key_var = var_store.fresh();
let val_var = Variable::EMPTY_RECORD;
let body = RunLowLevel {
op: LowLevel::DictInsert,
args: vec![
(dict_var, Var(Symbol::ARG_1)),
(key_var, Var(Symbol::ARG_2)),
(val_var, EmptyRecord),
],
ret_var: dict_var,
};
defn(
symbol,
vec![(dict_var, Symbol::ARG_1), (key_var, Symbol::ARG_2)],
var_store,
body,
dict_var,
)
}
/// Set.single : k -> Set k
fn set_single(symbol: Symbol, var_store: &mut VarStore) -> Def {
let key_var = var_store.fresh();
let set_var = var_store.fresh();
let value_var = Variable::EMPTY_RECORD;
let empty = RunLowLevel {
op: LowLevel::DictEmpty,
args: vec![],
ret_var: set_var,
};
let body = RunLowLevel {
op: LowLevel::DictInsert,
args: vec![
(set_var, empty),
(key_var, Var(Symbol::ARG_1)),
(value_var, EmptyRecord),
],
ret_var: set_var,
};
defn(
symbol,
vec![(key_var, Symbol::ARG_1)],
var_store,
body,
set_var,
)
}

View file

@ -1,8 +1,12 @@
use roc_can::{
use crate::{
def::Def,
expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData, WhenBranch},
expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData},
pattern::{DestructType, Pattern, RecordDestruct},
};
use roc_module::{
ident::{Lowercase, TagName},
symbol::Symbol,
};
use roc_module::ident::{Lowercase, TagName};
use roc_types::{
subs::{
self, AliasVariables, Descriptor, GetSubsSlice, OptVariable, RecordFields, Subs, SubsIndex,
@ -62,6 +66,8 @@ trait CopyEnv {
fn clone_tag_names(&mut self, tag_names: SubsSlice<TagName>) -> SubsSlice<TagName>;
fn clone_lambda_names(&mut self, lambda_names: SubsSlice<Symbol>) -> SubsSlice<Symbol>;
fn clone_record_fields(
&mut self,
record_fields: SubsSlice<RecordField<()>>,
@ -104,6 +110,11 @@ impl CopyEnv for Subs {
tag_names
}
#[inline(always)]
fn clone_lambda_names(&mut self, lambda_names: SubsSlice<Symbol>) -> SubsSlice<Symbol> {
lambda_names
}
#[inline(always)]
fn clone_record_fields(
&mut self,
@ -160,6 +171,14 @@ impl<'a> CopyEnv for AcrossSubs<'a> {
)
}
#[inline(always)]
fn clone_lambda_names(&mut self, lambda_names: SubsSlice<Symbol>) -> SubsSlice<Symbol> {
SubsSlice::extend_new(
&mut self.target.closure_names,
self.source.get_subs_slice(lambda_names).iter().cloned(),
)
}
#[inline(always)]
fn clone_record_fields(
&mut self,
@ -177,7 +196,7 @@ pub fn deep_copy_type_vars_into_expr(
var: Variable,
expr: &Expr,
) -> Option<(Variable, Expr)> {
deep_copy_type_vars_into_expr_help(subs, var, expr)
deep_copy_expr_top(subs, var, expr)
}
#[allow(unused)] // TODO to be removed when this is used for the derivers
@ -186,14 +205,14 @@ pub fn deep_copy_expr_across_subs(
target: &mut Subs,
var: Variable,
expr: &Expr,
) -> Option<(Variable, Expr)> {
) -> (Variable, Expr) {
let mut across_subs = AcrossSubs { source, target };
deep_copy_type_vars_into_expr_help(&mut across_subs, var, expr)
deep_copy_expr_top(&mut across_subs, var, expr).unwrap()
}
/// Deep copies all type variables in [`expr`].
/// Returns [`None`] if the expression does not need to be copied.
fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
fn deep_copy_expr_top<C: CopyEnv>(
env: &mut C,
var: Variable,
expr: &Expr,
@ -209,7 +228,7 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
return None;
}
let copied_expr = help(env, expr, &mut copied);
let copied_expr = deep_copy_expr_help(env, &mut copied, expr);
// we have tracked all visited variables, and can now traverse them
// in one go (without looking at the UnificationTable) and clear the copy field
@ -217,9 +236,10 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
env.clear_source_copy(var);
}
return Some((copy_expr_var, copied_expr));
Some((copy_expr_var, copied_expr))
}
fn help<C: CopyEnv>(env: &mut C, expr: &Expr, copied: &mut Vec<Variable>) -> Expr {
fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr: &Expr) -> Expr {
use Expr::*;
macro_rules! sub {
@ -230,16 +250,14 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
macro_rules! go_help {
($expr:expr) => {{
help(env, $expr, copied)
deep_copy_expr_help(env, copied, $expr)
}};
}
match expr {
Num(var, str, val, bound) => Num(sub!(*var), str.clone(), *val, *bound),
Int(v1, v2, str, val, bound) => Int(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
Float(v1, v2, str, val, bound) => {
Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound)
}
Float(v1, v2, str, val, bound) => Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
Str(str) => Str(str.clone()),
SingleQuote(char) => SingleQuote(*char),
List {
@ -269,13 +287,16 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
branches: branches
.iter()
.map(
|WhenBranch {
|crate::expr::WhenBranch {
patterns,
value,
guard,
redundant,
}| WhenBranch {
patterns: patterns.clone(),
}| crate::expr::WhenBranch {
patterns: patterns
.iter()
.map(|lp| lp.map(|p| deep_copy_pattern_help(env, copied, p)))
.collect(),
value: value.map(|e| go_help!(e)),
guard: guard.as_ref().map(|le| le.map(|e| go_help!(e))),
redundant: *redundant,
@ -310,13 +331,12 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
pattern_vars,
annotation,
}| Def {
loc_pattern: loc_pattern.clone(),
loc_pattern: loc_pattern.map(|p| deep_copy_pattern_help(env, copied, p)),
loc_expr: loc_expr.map(|e| go_help!(e)),
expr_var: sub!(*expr_var),
pattern_vars: pattern_vars
.iter()
.map(|(s, v)| (*s, sub!(*v)))
.collect(),
pattern_vars: pattern_vars.iter().map(|(s, v)| (*s, sub!(*v))).collect(),
// Annotation should only be used in constraining, don't clone before
// constraining :)
annotation: annotation.clone(),
},
)
@ -333,10 +353,12 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
annotation,
} = &**def;
let def = Def {
loc_pattern: loc_pattern.clone(),
loc_pattern: loc_pattern.map(|p| deep_copy_pattern_help(env, copied, p)),
loc_expr: loc_expr.map(|e| go_help!(e)),
expr_var: sub!(*expr_var),
pattern_vars: pattern_vars.iter().map(|(s, v)| (*s, sub!(*v))).collect(),
// Annotation should only be used in constraining, don't clone before
// constraining :)
annotation: annotation.clone(),
};
LetNonRec(Box::new(def), Box::new(body.map(|e| go_help!(e))))
@ -399,7 +421,13 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
recursive: *recursive,
arguments: arguments
.iter()
.map(|(v, mark, pat)| (sub!(*v), *mark, pat.clone()))
.map(|(v, mark, pat)| {
(
sub!(*v),
*mark,
pat.map(|p| deep_copy_pattern_help(env, copied, p)),
)
})
.collect(),
loc_body: Box::new(loc_body.map(|e| go_help!(e))),
}),
@ -581,6 +609,114 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
RuntimeError(err) => RuntimeError(err.clone()),
}
}
fn deep_copy_pattern_help<C: CopyEnv>(
env: &mut C,
copied: &mut Vec<Variable>,
pat: &Pattern,
) -> Pattern {
use Pattern::*;
macro_rules! sub {
($var:expr) => {{
deep_copy_type_vars(env, copied, $var)
}};
}
macro_rules! go_help {
($pat:expr) => {{
deep_copy_pattern_help(env, copied, $pat)
}};
}
match pat {
Identifier(s) => Identifier(*s),
AppliedTag {
whole_var,
ext_var,
tag_name,
arguments,
} => AppliedTag {
whole_var: sub!(*whole_var),
ext_var: sub!(*ext_var),
tag_name: tag_name.clone(),
arguments: arguments
.iter()
.map(|(var, lp)| (sub!(*var), lp.map(|p| go_help!(p))))
.collect(),
},
UnwrappedOpaque {
whole_var,
opaque,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => {
let (arg_var, arg_pat) = &**argument;
UnwrappedOpaque {
whole_var: sub!(*whole_var),
opaque: *opaque,
argument: Box::new((sub!(*arg_var), arg_pat.map(|p| go_help!(p)))),
// These are only used for constraining, they shouldn't matter where pattern
// cloning is useful (in monomorphization or during deriving)
specialized_def_type: specialized_def_type.clone(),
type_arguments: type_arguments.clone(),
lambda_set_variables: lambda_set_variables.clone(),
}
}
RecordDestructure {
whole_var,
ext_var,
destructs,
} => RecordDestructure {
whole_var: sub!(*whole_var),
ext_var: sub!(*ext_var),
destructs: destructs
.iter()
.map(|lrd| {
lrd.map(
|RecordDestruct {
var,
label,
symbol,
typ,
}| RecordDestruct {
var: sub!(*var),
label: label.clone(),
symbol: *symbol,
typ: match typ {
DestructType::Required => DestructType::Required,
DestructType::Optional(var, expr) => DestructType::Optional(
sub!(*var),
expr.map(|e| deep_copy_expr_help(env, copied, e)),
),
DestructType::Guard(var, pat) => {
DestructType::Guard(sub!(*var), pat.map(|p| go_help!(p)))
}
},
},
)
})
.collect(),
},
NumLiteral(var, s, n, bound) => NumLiteral(sub!(*var), s.clone(), *n, *bound),
IntLiteral(v1, v2, s, n, bound) => IntLiteral(sub!(*v1), sub!(*v2), s.clone(), *n, *bound),
FloatLiteral(v1, v2, s, n, bound) => {
FloatLiteral(sub!(*v1), sub!(*v2), s.clone(), *n, *bound)
}
StrLiteral(s) => StrLiteral(s.clone()),
SingleQuote(c) => SingleQuote(*c),
Underscore => Underscore,
AbilityMemberSpecialization { ident, specializes } => AbilityMemberSpecialization {
ident: *ident,
specializes: *specializes,
},
Shadowed(region, ident, symbol) => Shadowed(*region, ident.clone(), *symbol),
OpaqueNotInScope(ident) => OpaqueNotInScope(ident.clone()),
UnsupportedPattern(region) => UnsupportedPattern(*region),
MalformedPattern(problem, region) => MalformedPattern(*problem, *region),
}
}
/// Deep copies the type variables in [`var`], returning a map of original -> new type variable for
@ -849,8 +985,10 @@ fn deep_copy_type_vars<C: CopyEnv>(
env.target().variable_slices[target_index] = new_variables;
}
let new_solved_labels = env.clone_lambda_names(solved.labels());
let new_solved =
UnionLambdas::from_slices(solved.labels(), new_variable_slices);
UnionLambdas::from_slices(new_solved_labels, new_variable_slices);
let new_unspecialized =
SubsSlice::reserve_uls_slice(env.target(), unspecialized.len());
@ -887,10 +1025,12 @@ fn deep_copy_type_vars<C: CopyEnv>(
#[cfg(test)]
mod test {
use crate::copy::{deep_copy_type_vars_into_expr, AcrossSubs};
use crate::{
copy::{deep_copy_type_vars_into_expr, AcrossSubs},
expr::Expr,
};
use super::{deep_copy_expr_across_subs, deep_copy_type_vars};
use roc_can::expr::Expr;
use roc_error_macros::internal_error;
use roc_module::{ident::TagName, symbol::Symbol};
use roc_region::all::Loc;
@ -1105,8 +1245,7 @@ mod test {
)],
};
let (var, expr) =
deep_copy_expr_across_subs(&mut source, &mut target, var1, &expr).unwrap();
let (var, expr) = deep_copy_expr_across_subs(&mut source, &mut target, var1, &expr);
assert!(source.get_copy(var1).is_none());
assert!(source.get_copy(var2).is_none());

View file

@ -155,7 +155,7 @@ enum PendingTypeDef<'a> {
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
derived: Option<&'a Loc<ast::Derived<'a>>>,
derived: Option<&'a Loc<ast::HasAbilities<'a>>>,
},
Ability {
@ -425,7 +425,7 @@ fn canonicalize_opaque<'a>(
name: Loc<Symbol>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
vars: &[Loc<Lowercase>],
derives: Option<&'a Loc<ast::Derived<'a>>>,
has_abilities: Option<&'a Loc<ast::HasAbilities<'a>>>,
) -> Result<Alias, ()> {
let alias = canonicalize_alias(
env,
@ -439,18 +439,22 @@ fn canonicalize_opaque<'a>(
AliasKind::Opaque,
)?;
if let Some(derives) = derives {
let derives = derives.value.collection();
if let Some(has_abilities) = has_abilities {
let has_abilities = has_abilities.value.collection();
let mut can_derives = vec![];
let mut can_abilities = vec![];
for derived in derives.items {
let region = derived.region;
match derived.value.extract_spaces().item {
for has_ability in has_abilities.items {
let region = has_ability.region;
let (ability, _impls) = match has_ability.value.extract_spaces().item {
ast::HasAbility::HasAbility { ability, impls } => (ability, impls),
_ => internal_error!("spaces not extracted"),
};
match ability.value {
ast::TypeAnnotation::Apply(module_name, ident, []) => {
match make_apply_symbol(env, region, scope, module_name, ident) {
Ok(ability) if ability.is_builtin_ability() => {
can_derives.push(Loc::at(region, ability));
can_abilities.push(Loc::at(region, ability));
}
Ok(_) => {
// Register the problem but keep going, we may still be able to compile the
@ -471,7 +475,7 @@ fn canonicalize_opaque<'a>(
}
}
if !can_derives.is_empty() {
if !can_abilities.is_empty() {
// Fresh instance of this opaque to be checked for derivability during solving.
let fresh_inst = Type::DelayedAlias(AliasCommon {
symbol: name.value,
@ -489,7 +493,7 @@ fn canonicalize_opaque<'a>(
let old = output
.pending_derives
.insert(name.value, (fresh_inst, can_derives));
.insert(name.value, (fresh_inst, can_abilities));
debug_assert!(old.is_none());
}
}
@ -701,7 +705,7 @@ fn canonicalize_type_defs<'a>(
Loc<Symbol>,
Vec<Loc<Lowercase>>,
&'a Loc<ast::TypeAnnotation<'a>>,
Option<&'a Loc<ast::Derived<'a>>>,
Option<&'a Loc<ast::HasAbilities<'a>>>,
),
Ability(Loc<Symbol>, &'a [AbilityMember<'a>]),
}
@ -1885,7 +1889,7 @@ fn to_pending_alias_or_opaque<'a>(
name: &'a Loc<&'a str>,
vars: &'a [Loc<ast::Pattern<'a>>],
ann: &'a Loc<ast::TypeAnnotation<'a>>,
opt_derived: Option<&'a Loc<ast::Derived<'a>>>,
opt_derived: Option<&'a Loc<ast::HasAbilities<'a>>>,
kind: AliasKind,
) -> PendingTypeDef<'a> {
let shadow_kind = match kind {

View file

@ -10,6 +10,7 @@ use crate::num::{
use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern};
use crate::procedure::References;
use crate::scope::Scope;
use crate::traverse::{walk_expr, Visitor};
use roc_collections::soa::Index;
use roc_collections::{SendMap, VecMap, VecSet};
use roc_error_macros::internal_error;
@ -471,6 +472,15 @@ pub enum Recursive {
TailRecursive = 2,
}
impl Recursive {
pub fn is_recursive(&self) -> bool {
match self {
Recursive::NotRecursive => false,
Recursive::Recursive | Recursive::TailRecursive => true,
}
}
}
#[derive(Clone, Debug)]
pub struct WhenBranch {
pub patterns: Vec<Loc<Pattern>>,
@ -2346,6 +2356,38 @@ impl Declarations {
Some((length_so_far, *e))
})
}
pub fn expects(&self) -> VecMap<Region, Vec<(Symbol, Variable)>> {
let mut collector = ExpectCollector {
expects: VecMap::default(),
};
let var = Variable::EMPTY_RECORD;
for index in 0..self.len() {
use crate::expr::DeclarationTag::*;
match self.declarations[index] {
Value | Function(_) | Recursive(_) | TailRecursive(_) | Destructure(_) => {
// def pattern has no default expressions, so skip
let loc_expr = &self.expressions[index];
collector.visit_expr(&loc_expr.value, loc_expr.region, var);
}
MutualRecursion { .. } => {
// the self of this group will be treaded individually by later iterations
}
Expectation => {
let loc_expr =
toplevel_expect_to_inline_expect(self.expressions[index].clone());
collector.visit_expr(&loc_expr.value, loc_expr.region, var);
}
}
}
collector.expects
}
}
roc_error_macros::assert_sizeof_default!(DeclarationTag, 8);
@ -2574,3 +2616,20 @@ pub fn toplevel_expect_to_inline_expect(mut loc_expr: Loc<Expr>) -> Loc<Expr> {
loc_expr
}
struct ExpectCollector {
expects: VecMap<Region, Vec<(Symbol, Variable)>>,
}
impl crate::traverse::Visitor for ExpectCollector {
fn visit_expr(&mut self, expr: &Expr, region: Region, var: Variable) {
if let Expr::Expect {
lookups_in_cond, ..
} = expr
{
self.expects.insert(region, lookups_in_cond.to_vec());
}
walk_expr(self, expr, var)
}
}

View file

@ -5,6 +5,7 @@ pub mod abilities;
pub mod annotation;
pub mod builtins;
pub mod constraint;
pub mod copy;
pub mod def;
pub mod effect_module;
pub mod env;

View file

@ -7,7 +7,7 @@ use crate::expr::{ClosureData, Declarations, Expr, Output, PendingDerives};
use crate::pattern::{BindingsFromPattern, Pattern};
use crate::scope::Scope;
use bumpalo::Bump;
use roc_collections::{MutMap, SendMap, VecSet};
use roc_collections::{MutMap, SendMap, VecMap, VecSet};
use roc_error_macros::internal_error;
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
@ -122,6 +122,7 @@ pub struct Module {
pub aliases: MutMap<Symbol, (bool, Alias)>,
pub rigid_variables: RigidVariables,
pub abilities_store: PendingAbilitiesStore,
pub loc_expects: VecMap<Region, Vec<(Symbol, Variable)>>,
}
#[derive(Debug, Default)]
@ -144,6 +145,7 @@ pub struct ModuleOutput {
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
pub pending_derives: PendingDerives,
pub scope: Scope,
pub loc_expects: VecMap<Region, Vec<(Symbol, Variable)>>,
}
fn validate_generate_with<'a>(
@ -328,15 +330,7 @@ pub fn canonicalize_module_defs<'a>(
panic!("TODO gracefully handle shadowing in imports.")
}
}
} else if [
Symbol::LIST_LIST,
Symbol::STR_STR,
Symbol::DICT_DICT,
Symbol::SET_SET,
Symbol::BOX_BOX_TYPE,
]
.contains(&symbol)
{
} else if [Symbol::LIST_LIST, Symbol::STR_STR, Symbol::BOX_BOX_TYPE].contains(&symbol) {
// These are not aliases but Apply's and we make sure they are always in scope
} else {
// This is a type alias or ability
@ -743,6 +737,8 @@ pub fn canonicalize_module_defs<'a>(
}
}
let loc_expects = declarations.expects();
ModuleOutput {
scope,
aliases,
@ -755,6 +751,7 @@ pub fn canonicalize_module_defs<'a>(
symbols_from_requires,
pending_derives,
lookups,
loc_expects,
}
}

View file

@ -56,6 +56,13 @@ impl Scope {
self.lookup_str(ident.as_str(), region)
}
pub fn add_docs_imports(&mut self) {
self.imports
.push(("Dict".into(), Symbol::DICT_DICT, Region::zero()));
self.imports
.push(("Set".into(), Symbol::SET_SET, Region::zero()));
}
pub fn lookup_str(&self, ident: &str, region: Region) -> Result<Symbol, RuntimeError> {
use ContainsIdent::*;
@ -653,8 +660,6 @@ mod test {
Ident::from("List"),
Ident::from("Ok"),
Ident::from("Err"),
Ident::from("Dict"),
Ident::from("Set"),
Ident::from("Box"),
]
);
@ -680,8 +685,6 @@ mod test {
Ident::from("List"),
Ident::from("Ok"),
Ident::from("Err"),
Ident::from("Dict"),
Ident::from("Set"),
Ident::from("Box"),
]
);

View file

@ -20,7 +20,7 @@ use roc_types::subs::{
};
use roc_types::types::{AliasKind, RecordField};
use crate::{synth_var, DerivedBody, DERIVED_MODULE};
use crate::{synth_var, DerivedBody, DERIVED_SYNTH};
macro_rules! bad_input {
($subs:expr, $var:expr) => {
@ -64,7 +64,7 @@ impl Env<'_> {
let ident_id = self.derived_ident_ids.get_or_insert(&debug_name);
Symbol::new(DERIVED_MODULE, ident_id)
Symbol::new(DERIVED_SYNTH, ident_id)
} else {
self.unique_symbol()
}
@ -72,7 +72,7 @@ impl Env<'_> {
fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.derived_ident_ids.gen_unique();
Symbol::new(DERIVED_MODULE, ident_id)
Symbol::new(DERIVED_SYNTH, ident_id)
}
fn import_encode_symbol(&mut self, symbol: Symbol) -> Variable {
@ -219,7 +219,6 @@ pub(crate) fn derive_to_encoder(
def_symbol: Symbol,
) -> DerivedBody {
let (body, body_type) = match key {
FlatEncodableKey::String => to_encoder_string(env, def_symbol),
FlatEncodableKey::List() => todo!(),
FlatEncodableKey::Set() => todo!(),
FlatEncodableKey::Dict() => todo!(),
@ -277,97 +276,6 @@ pub(crate) fn derive_to_encoder(
}
}
fn to_encoder_string(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
// Build \s -> Encode.string s
use Expr::*;
let s_sym = env.new_symbol("s");
// build `Encode.string s` type
// Str -[uls]-> Encoder fmt | fmt has EncoderFormatting
let encode_string_fn_var = env.import_encode_symbol(Symbol::ENCODE_STRING);
// Str -[clos]-> t1
let string_var_slice = VariableSubsSlice::insert_into_subs(env.subs, once(Variable::STR)); // TODO: consider caching this singleton slice
let encode_string_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1
let this_encode_string_fn_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(
string_var_slice,
encode_string_clos_var,
encoder_var,
)),
);
// Str -[uls]-> Encoder fmt | fmt has EncoderFormatting
// ~ Str -[clos]-> t1
env.unify(encode_string_fn_var, this_encode_string_fn_var);
// Encode.string : Str -[clos]-> Encoder fmt | fmt has EncoderFormatting
let encode_string_var = AbilityMember(Symbol::ENCODE_STRING, None, encode_string_fn_var);
let encode_record_fn = Box::new((
encode_string_fn_var,
Loc::at_zero(encode_string_var),
encode_string_clos_var,
encoder_var,
));
// Encode.string s
let encode_string_call = Call(
encode_record_fn,
vec![(Variable::STR, Loc::at_zero(Var(s_sym)))],
CalledVia::Space,
);
// Encode.custom \bytes, fmt -> Encode.appendWith bytes (Encode.string s) fmt
let (body, this_encoder_var) =
wrap_in_encode_custom(env, encode_string_call, encoder_var, s_sym, Variable::STR);
// 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,
}),
);
// Str -[fn_name]-> (typeof Encode.record [ .. ] = Encoder fmt)
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(
string_var_slice,
fn_clos_var,
this_encoder_var,
)),
);
// \rcd -[fn_name]-> Encode.record [ { 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![(
Variable::STR,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(s_sym)),
)],
loc_body: Box::new(Loc::at_zero(body)),
});
(clos, fn_var)
}
fn to_encoder_record(
env: &mut Env<'_>,
record_var: Variable,

View file

@ -3,6 +3,7 @@
use std::iter::once;
use std::sync::{Arc, Mutex};
use roc_can::abilities::SpecializationLambdaSets;
use roc_can::expr::Expr;
use roc_can::pattern::Pattern;
use roc_can::{def::Def, module::ExposedByModule};
@ -16,9 +17,7 @@ use roc_types::subs::{
mod encoding;
type SpecializationLambdaSets = VecMap<u8, Variable>;
pub(crate) const DERIVED_MODULE: ModuleId = ModuleId::DERIVED;
pub(crate) const DERIVED_SYNTH: ModuleId = ModuleId::DERIVED_SYNTH;
pub fn synth_var(subs: &mut Subs, content: Content) -> Variable {
let descriptor = Descriptor {
@ -34,20 +33,13 @@ pub fn synth_var(subs: &mut Subs, content: Content) -> Variable {
}
/// Map of [`DeriveKey`]s to their derived symbols.
///
/// This represents the [`Derived_synth`][Symbol::DERIVED_SYNTH] module.
#[derive(Debug, Default)]
pub struct DerivedModule {
map: MutMap<DeriveKey, (Symbol, Def, SpecializationLambdaSets)>,
subs: Subs,
derived_ident_ids: IdentIds,
/// Has someone stolen subs/ident ids from us?
#[cfg(debug_assertions)]
stolen: bool,
}
pub struct StolenFromDerived {
pub subs: Subs,
pub ident_ids: IdentIds,
}
pub(crate) struct DerivedBody {
@ -98,31 +90,30 @@ impl DerivedModule {
exposed_by_module: &ExposedByModule,
key: DeriveKey,
) -> &(Symbol, Def, SpecializationLambdaSets) {
#[cfg(debug_assertions)]
{
debug_assert!(!self.stolen, "attempting to add to stolen symbols!");
match self.map.get(&key) {
Some(entry) => {
// rustc won't let us return an immutable reference *and* continue using
// `self.map` immutably below, but this is safe, because we are not returning
// an immutable reference to the entry.
return unsafe { std::mem::transmute(entry) };
}
None => {}
}
// TODO: can we get rid of the clone?
let entry = self.map.entry(key.clone());
entry.or_insert_with(|| {
let ident_id = if cfg!(any(
debug_assertions,
test,
feature = "debug-derived-symbols"
)) {
let ident_id = if cfg!(debug_assertions) || cfg!(feature = "debug-derived-symbols") {
let debug_name = key.debug_name();
debug_assert!(
self.derived_ident_ids.get_id(&debug_name).is_none(),
"duplicate debug name for different derive key"
);
self.derived_ident_ids.get_or_insert(&debug_name)
let ident_id = self.derived_ident_ids.get_or_insert(&debug_name);
// This is expensive, but yields much better symbols when debugging.
// TODO: hide behind debug_flags?
DERIVED_SYNTH.register_debug_idents(&self.derived_ident_ids);
ident_id
} else {
self.derived_ident_ids.gen_unique()
};
let derived_symbol = Symbol::new(DERIVED_MODULE, ident_id);
let derived_symbol = Symbol::new(DERIVED_SYNTH, ident_id);
let (derived_def, specialization_lsets) = build_derived_body(
&mut self.subs,
&mut self.derived_ident_ids,
@ -131,18 +122,13 @@ impl DerivedModule {
key.clone(),
);
(derived_symbol, derived_def, specialization_lsets)
})
let triple = (derived_symbol, derived_def, specialization_lsets);
self.map.entry(key).or_insert(triple)
}
pub fn iter_all(
&self,
) -> impl Iterator<Item = (&DeriveKey, &(Symbol, Def, SpecializationLambdaSets))> {
#[cfg(debug_assertions)]
{
debug_assert!(!self.stolen);
}
self.map.iter()
}
@ -150,57 +136,25 @@ impl DerivedModule {
/// module; other modules should use [`Self::get_or_insert`] to generate a symbol for a derived
/// ability member usage.
pub fn gen_unique(&mut self) -> Symbol {
#[cfg(debug_assertions)]
{
debug_assert!(!self.stolen);
}
let ident_id = self.derived_ident_ids.gen_unique();
Symbol::new(DERIVED_MODULE, ident_id)
Symbol::new(DERIVED_SYNTH, ident_id)
}
/// Steal all created derived ident Ids.
/// After this is called, [`Self::get_or_insert`] may no longer be called.
pub fn steal(&mut self) -> StolenFromDerived {
let mut ident_ids = Default::default();
std::mem::swap(&mut self.derived_ident_ids, &mut ident_ids);
let mut subs = Default::default();
std::mem::swap(&mut self.subs, &mut subs);
#[cfg(debug_assertions)]
{
debug_assert!(!self.stolen);
self.stolen = true;
}
StolenFromDerived { subs, ident_ids }
}
pub fn return_stolen(&mut self, stolen: StolenFromDerived) {
#[cfg(debug_assertions)]
{
debug_assert!(self.stolen);
self.stolen = false;
}
let StolenFromDerived { subs, ident_ids } = stolen;
self.subs = subs;
self.derived_ident_ids = ident_ids;
}
pub fn copy_lambda_set_var_to_subs(&self, var: Variable, target: &mut Subs) -> Variable {
#[cfg(debug_assertions)]
{
debug_assert!(!self.stolen);
}
// TODO: just pass and copy the ambient function directly, don't pass the lambda set var.
pub fn copy_lambda_set_ambient_function_to_subs(
&self,
lambda_set_var: Variable,
target: &mut Subs,
_target_rank: Rank,
) -> Variable {
let ambient_function_var = self.subs.get_lambda_set(lambda_set_var).ambient_function;
let copied_import = copy_import_to(
&self.subs,
target,
// bookkeep unspecialized lambda sets of var - I think don't want this here
false,
var,
// bookkeep unspecialized lambda sets of var - I think we want this here
true,
ambient_function_var,
// TODO: I think this is okay because the only use of `copy_lambda_set_var_to_subs`
// (at least right now) is for lambda set compaction, which will automatically unify
// and lower ranks, and never generalize.
@ -208,11 +162,52 @@ impl DerivedModule {
// However this is a bad coupling and maybe not a good assumption, we should revisit
// this when possible.
Rank::import(),
// target_rank,
);
copied_import.variable
}
/// Gets the derived defs that should be loaded into the derived gen module, skipping over the
/// defs that have already been loaded.
pub fn iter_load_for_gen_module(
&mut self,
gen_subs: &mut Subs,
should_load_def: impl Fn(Symbol) -> bool,
) -> VecMap<Symbol, Expr> {
self.map
.values()
.filter_map(|(symbol, def, _)| {
if should_load_def(*symbol) {
let (_new_expr_var, new_expr) = roc_can::copy::deep_copy_expr_across_subs(
&mut self.subs,
gen_subs,
def.expr_var,
&def.loc_expr.value,
);
Some((*symbol, new_expr))
} else {
None
}
})
.collect()
}
/// Thread-sharable [`DerivedMethods`].
/// # Safety
///
/// Prefer using a fresh Derived module with [`Derived::default`]. Use this only in testing.
pub unsafe fn from_components(subs: Subs, ident_ids: IdentIds) -> Self {
Self {
map: Default::default(),
subs,
derived_ident_ids: ident_ids,
}
}
pub fn decompose(self) -> (Subs, IdentIds) {
(self.subs, self.derived_ident_ids)
}
}
/// Thread-sharable [`DerivedModule`].
pub type SharedDerivedModule = Arc<Mutex<DerivedModule>>;

View file

@ -11,7 +11,4 @@ roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
[features]
default = []
debug-derived-symbols = []
roc_can = { path = "../can" }

View file

@ -14,7 +14,6 @@ pub enum FlatEncodable {
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
pub enum FlatEncodableKey {
String,
List(/* takes one variable */),
Set(/* takes one variable */),
Dict(/* takes two variables */),
@ -26,7 +25,6 @@ pub enum FlatEncodableKey {
impl FlatEncodableKey {
pub(crate) fn debug_name(&self) -> String {
match self {
FlatEncodableKey::String => "string".to_string(),
FlatEncodableKey::List() => "list".to_string(),
FlatEncodableKey::Set() => "set".to_string(),
FlatEncodableKey::Dict() => "dict".to_string(),
@ -84,7 +82,7 @@ impl FlatEncodable {
Symbol::LIST_LIST => Ok(Key(FlatEncodableKey::List())),
Symbol::SET_SET => Ok(Key(FlatEncodableKey::Set())),
Symbol::DICT_DICT => Ok(Key(FlatEncodableKey::Dict())),
Symbol::STR_STR => Ok(Key(FlatEncodableKey::String)),
Symbol::STR_STR => Ok(Immediate(Symbol::ENCODE_STRING)),
_ => Err(Underivable),
},
FlatType::Record(fields, ext) => {
@ -134,19 +132,19 @@ impl FlatEncodable {
FlatType::Func(..) => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match sym {
Symbol::NUM_U8 => Ok(Immediate(Symbol::ENCODE_U8)),
Symbol::NUM_U16 => Ok(Immediate(Symbol::ENCODE_U16)),
Symbol::NUM_U32 => Ok(Immediate(Symbol::ENCODE_U32)),
Symbol::NUM_U64 => Ok(Immediate(Symbol::ENCODE_U64)),
Symbol::NUM_U128 => Ok(Immediate(Symbol::ENCODE_U128)),
Symbol::NUM_I8 => Ok(Immediate(Symbol::ENCODE_I8)),
Symbol::NUM_I16 => Ok(Immediate(Symbol::ENCODE_I16)),
Symbol::NUM_I32 => Ok(Immediate(Symbol::ENCODE_I32)),
Symbol::NUM_I64 => Ok(Immediate(Symbol::ENCODE_I64)),
Symbol::NUM_I128 => Ok(Immediate(Symbol::ENCODE_I128)),
Symbol::NUM_DEC => Ok(Immediate(Symbol::ENCODE_DEC)),
Symbol::NUM_F32 => Ok(Immediate(Symbol::ENCODE_F32)),
Symbol::NUM_F64 => Ok(Immediate(Symbol::ENCODE_F64)),
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)),
// 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),

View file

@ -15,12 +15,9 @@
pub mod encoding;
use std::sync::{Arc, Mutex};
use encoding::{FlatEncodable, FlatEncodableKey};
use roc_collections::MutMap;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::Symbol;
use roc_types::subs::{Subs, Variable};
#[derive(Debug, PartialEq)]
@ -68,54 +65,3 @@ impl Derived {
}
}
}
/// Map of [`DeriveKey`]s to their derived symbols.
#[derive(Debug, Default)]
pub struct DerivedSymbols {
map: MutMap<DeriveKey, Symbol>,
derived_ident_ids: IdentIds,
#[cfg(debug_assertions)]
stolen: bool,
}
impl DerivedSymbols {
pub fn get_or_insert(&mut self, key: DeriveKey) -> Symbol {
#[cfg(debug_assertions)]
{
debug_assert!(!self.stolen, "attempting to add to stolen symbols!");
}
let symbol = self.map.entry(key).or_insert_with_key(|key| {
let ident_id = if cfg!(debug_assertions) || cfg!(feature = "debug-derived-symbols") {
let debug_name = key.debug_name();
debug_assert!(
self.derived_ident_ids.get_id(&debug_name).is_none(),
"duplicate debug name for different derive key"
);
self.derived_ident_ids.get_or_insert(&debug_name)
} else {
self.derived_ident_ids.gen_unique()
};
Symbol::new(ModuleId::DERIVED, ident_id)
});
*symbol
}
/// Steal all created derived ident Ids.
/// After this is called, [`Self::get_or_insert`] may no longer be called.
pub fn steal(&mut self) -> IdentIds {
let mut ident_ids = Default::default();
std::mem::swap(&mut self.derived_ident_ids, &mut ident_ids);
#[cfg(debug_assertions)]
{
self.stolen = true;
}
ident_ids
}
}
/// Thread-sharable [`DerivedMethods`].
pub type GlobalDerivedSymbols = Arc<Mutex<DerivedSymbols>>;

View file

@ -4,8 +4,8 @@ use crate::{
Buf,
};
use roc_parse::ast::{
AssignedField, Collection, Derived, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation,
TypeHeader,
AssignedField, Collection, Expr, ExtractSpaces, HasAbilities, HasAbility, HasClause, HasImpls,
Tag, TypeAnnotation, TypeHeader,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -563,11 +563,11 @@ impl<'a> Formattable for HasClause<'a> {
}
}
impl<'a> Formattable for Derived<'a> {
impl<'a> Formattable for HasImpls<'a> {
fn is_multiline(&self) -> bool {
match self {
Derived::SpaceAfter(..) | Derived::SpaceBefore(..) => true,
Derived::Has(derived) => derived.is_multiline(),
HasImpls::SpaceBefore(_, _) | HasImpls::SpaceAfter(_, _) => true,
HasImpls::HasImpls(impls) => impls.is_multiline(),
}
}
@ -579,23 +579,103 @@ impl<'a> Formattable for Derived<'a> {
indent: u16,
) {
match self {
Derived::Has(derived) => {
HasImpls::HasImpls(impls) => {
if newlines == Newlines::Yes {
buf.newline();
buf.indent(indent);
}
fmt_collection(buf, indent, Braces::Curly, *impls, Newlines::No);
}
HasImpls::SpaceBefore(impls, spaces) => {
buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
impls.format_with_options(buf, parens, Newlines::No, indent);
}
HasImpls::SpaceAfter(impls, spaces) => {
impls.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
}
}
}
impl<'a> Formattable for HasAbility<'a> {
fn is_multiline(&self) -> bool {
match self {
HasAbility::SpaceAfter(..) | HasAbility::SpaceBefore(..) => true,
HasAbility::HasAbility { ability, impls } => {
ability.is_multiline() || impls.map(|i| i.is_multiline()).unwrap_or(false)
}
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
match self {
HasAbility::HasAbility { ability, impls } => {
if newlines == Newlines::Yes {
buf.newline();
buf.indent(indent);
}
ability.format_with_options(buf, parens, newlines, indent);
if let Some(impls) = impls {
buf.spaces(1);
impls.format_with_options(buf, parens, newlines, indent);
}
}
HasAbility::SpaceBefore(ab, spaces) => {
buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
ab.format_with_options(buf, parens, Newlines::No, indent)
}
HasAbility::SpaceAfter(ab, spaces) => {
ab.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
}
}
}
impl<'a> Formattable for HasAbilities<'a> {
fn is_multiline(&self) -> bool {
match self {
HasAbilities::SpaceAfter(..) | HasAbilities::SpaceBefore(..) => true,
HasAbilities::Has(has_abilities) => has_abilities.is_multiline(),
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
match self {
HasAbilities::Has(has_abilities) => {
if newlines == Newlines::Yes {
buf.newline();
buf.indent(indent);
}
buf.push_str("has");
buf.spaces(1);
fmt_collection(buf, indent, Braces::Square, *derived, newlines);
fmt_collection(buf, indent, Braces::Square, *has_abilities, Newlines::No);
}
Derived::SpaceBefore(derived, spaces) => {
HasAbilities::SpaceBefore(has_abilities, spaces) => {
buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
derived.format_with_options(buf, parens, Newlines::No, indent)
has_abilities.format_with_options(buf, parens, Newlines::No, indent)
}
Derived::SpaceAfter(derived, spaces) => {
derived.format_with_options(buf, parens, newlines, indent);
HasAbilities::SpaceAfter(has_abilities, spaces) => {
has_abilities.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
}

View file

@ -82,6 +82,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
}
}
buf.indent(item_indent);
item.item.format(buf, item_indent);
buf.push(',');

View file

@ -79,7 +79,7 @@ impl<'a> Formattable for TypeDef<'a> {
Opaque {
header: TypeHeader { name, vars },
typ: ann,
derived,
derived: has_abilities,
} => {
buf.indent(indent);
buf.push_str(name.value);
@ -95,28 +95,26 @@ impl<'a> Formattable for TypeDef<'a> {
let ann_is_where_clause =
matches!(ann.extract_spaces().item, TypeAnnotation::Where(..));
// Always put the has-derived clause on a newline if it is itself multiline, or
// the annotation has a where-has clause.
let derived_multiline = if let Some(derived) = derived {
!derived.value.is_empty() && (derived.is_multiline() || ann_is_where_clause)
// Always put the has-abilities clause on a newline if the opaque annotation
// contains a where-has clause.
let has_abilities_multiline = if let Some(has_abilities) = has_abilities {
!has_abilities.value.is_empty() && ann_is_where_clause
} else {
false
};
let make_multiline = ann.is_multiline() || derived_multiline;
let make_multiline = ann.is_multiline() || has_abilities_multiline;
ann.format(buf, indent);
if let Some(derived) = derived {
if !make_multiline {
if let Some(has_abilities) = has_abilities {
buf.spaces(1);
}
derived.format_with_options(
has_abilities.format_with_options(
buf,
Parens::NotNeeded,
Newlines::from_bool(make_multiline),
indent + INDENT,
indent + 1 + INDENT,
);
}
}

View file

@ -3,9 +3,9 @@ use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::{
ast::{
AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Derived, Expr, Has,
HasClause, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
TypeHeader, ValueDef, WhenBranch,
AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr, Has, HasAbilities,
HasAbility, HasClause, HasImpls, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag,
TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
@ -801,11 +801,36 @@ impl<'a> RemoveSpaces<'a> for Tag<'a> {
}
}
impl<'a> RemoveSpaces<'a> for Derived<'a> {
impl<'a> RemoveSpaces<'a> for HasImpls<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Derived::Has(derived) => Derived::Has(derived.remove_spaces(arena)),
Derived::SpaceBefore(derived, _) | Derived::SpaceAfter(derived, _) => {
HasImpls::HasImpls(impls) => HasImpls::HasImpls(impls.remove_spaces(arena)),
HasImpls::SpaceBefore(has, _) | HasImpls::SpaceAfter(has, _) => {
has.remove_spaces(arena)
}
}
}
}
impl<'a> RemoveSpaces<'a> for HasAbility<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
HasAbility::HasAbility { ability, impls } => HasAbility::HasAbility {
ability: ability.remove_spaces(arena),
impls: impls.remove_spaces(arena),
},
HasAbility::SpaceBefore(has, _) | HasAbility::SpaceAfter(has, _) => {
has.remove_spaces(arena)
}
}
}
}
impl<'a> RemoveSpaces<'a> for HasAbilities<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
HasAbilities::Has(derived) => HasAbilities::Has(derived.remove_spaces(arena)),
HasAbilities::SpaceBefore(derived, _) | HasAbilities::SpaceAfter(derived, _) => {
derived.remove_spaces(arena)
}
}

View file

@ -5229,6 +5229,86 @@ mod test_fmt {
"#
),
);
expr_formats_to(
indoc!(
r#"
A := U8 has []
0
"#
),
indoc!(
r#"
A := U8 has []
0
"#
),
);
}
#[test]
fn opaque_has_with_impls() {
expr_formats_same(indoc!(
r#"
A := U8 has [Eq { eq }, Hash { hash }]
0
"#
));
expr_formats_same(indoc!(
r#"
A := U8 has [Eq { eq, eq1 }]
0
"#
));
expr_formats_to(
indoc!(
r#"
A := U8 has [Eq { eq, eq1 }]
A := U8 has [Eq {
eq,
eq1
}]
0
"#
),
indoc!(
r#"
A := U8 has [Eq { eq, eq1 }]
A := U8 has [
Eq {
eq,
eq1,
},
]
0
"#
),
);
expr_formats_same(indoc!(
r#"
A := a | a has Other
has [Eq { eq }, Hash { hash }]
0
"#
));
expr_formats_same(indoc!(
r#"
A := U8 has [Eq {}]
0
"#
));
}
#[test]

View file

@ -2,12 +2,6 @@ use crate::llvm::bitcode::{
call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_list_bitcode_fn,
call_str_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build_dict::{
self, dict_capacity, dict_contains, dict_difference, dict_empty, dict_get, dict_insert,
dict_intersection, dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk,
set_capacity, set_from_list,
};
use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_capacity, list_concat,
list_drop_at, list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4,
@ -173,6 +167,41 @@ pub enum LlvmBackendMode {
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
GenTest,
WasmGenTest,
CliTest,
}
impl LlvmBackendMode {
pub(crate) fn has_host(self) -> bool {
match self {
LlvmBackendMode::Binary => true,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false,
LlvmBackendMode::CliTest => false,
}
}
/// In other words, catches exceptions and returns a result
fn returns_roc_result(self) -> bool {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::GenTest => true,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => true,
}
}
fn runs_expects(self) -> bool {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false,
LlvmBackendMode::CliTest => true,
}
}
fn runs_expects_in_separate_process(self) -> bool {
false
}
}
pub struct Env<'a, 'ctx, 'env> {
@ -2266,15 +2295,6 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
.into_pointer_value()
}
macro_rules! dict_key_value_layout {
($dict_layout:expr) => {
match $dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => (key_layout, value_layout),
_ => unreachable!("invalid dict layout"),
}
};
}
macro_rules! list_element_layout {
($list_layout:expr) => {
match $list_layout {
@ -2824,20 +2844,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
build_list::decref(env, value.into_struct_value(), alignment);
}
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
debug_assert!(value.is_struct_value());
let alignment = key_layout
.alignment_bytes(env.target_info)
.max(value_layout.alignment_bytes(env.target_info));
build_dict::decref(env, value.into_struct_value(), alignment);
}
Layout::Builtin(Builtin::Set(key_layout)) => {
debug_assert!(value.is_struct_value());
let alignment = key_layout.alignment_bytes(env.target_info);
build_dict::decref(env, value.into_struct_value(), alignment);
}
_ if layout.is_refcounted() => {
if value.is_pointer_value() {
@ -2876,18 +2882,16 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
}
Expect {
condition: cond,
region: _,
lookups: _,
condition: cond_symbol,
region,
lookups,
layouts: _,
remainder,
} => {
// do stuff
let bd = env.builder;
let context = env.context;
let (cond, _cond_layout) = load_symbol_and_layout(scope, cond);
let (cond, _cond_layout) = load_symbol_and_layout(scope, cond_symbol);
let condition = bd.build_int_compare(
IntPredicate::EQ,
@ -2901,19 +2905,131 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
bd.build_conditional_branch(condition, then_block, throw_block);
{
if env.mode.runs_expects() {
bd.position_at_end(throw_block);
match env.target_info.ptr_width() {
roc_target::PtrWidth::Bytes8 => {
// temporary native implementation
throw_exception(env, "An expectation failed!");
let func = env
.module
.get_function(bitcode::UTILS_EXPECT_FAILED_START)
.unwrap();
let call_result = bd.build_call(func, &[], "call_expect_start_failed");
let mut ptr = call_result
.try_as_basic_value()
.left()
.unwrap()
.into_pointer_value();
{
let value = env
.context
.i32_type()
.const_int(region.start().offset as _, false);
let cast_ptr = env.builder.build_pointer_cast(
ptr,
value.get_type().ptr_type(AddressSpace::Generic),
"to_store_pointer",
);
env.builder.build_store(cast_ptr, value);
// let increment = layout.stack_size(env.target_info);
let increment = 4;
let increment = env.ptr_int().const_int(increment as _, false);
ptr = unsafe {
env.builder.build_gep(ptr, &[increment], "increment_ptr")
};
}
{
let value = env
.context
.i32_type()
.const_int(region.end().offset as _, false);
let cast_ptr = env.builder.build_pointer_cast(
ptr,
value.get_type().ptr_type(AddressSpace::Generic),
"to_store_pointer",
);
env.builder.build_store(cast_ptr, value);
// let increment = layout.stack_size(env.target_info);
let increment = 4;
let increment = env.ptr_int().const_int(increment as _, false);
ptr = unsafe {
env.builder.build_gep(ptr, &[increment], "increment_ptr")
};
}
{
let region_bytes: u32 =
unsafe { std::mem::transmute(cond_symbol.module_id()) };
let value = env.context.i32_type().const_int(region_bytes as _, false);
let cast_ptr = env.builder.build_pointer_cast(
ptr,
value.get_type().ptr_type(AddressSpace::Generic),
"to_store_pointer",
);
env.builder.build_store(cast_ptr, value);
// let increment = layout.stack_size(env.target_info);
let increment = 4;
let increment = env.ptr_int().const_int(increment as _, false);
ptr = unsafe {
env.builder.build_gep(ptr, &[increment], "increment_ptr")
};
}
for lookup in lookups.iter() {
let (value, layout) = load_symbol_and_layout(scope, lookup);
let cast_ptr = env.builder.build_pointer_cast(
ptr,
value.get_type().ptr_type(AddressSpace::Generic),
"to_store_pointer",
);
store_roc_value(env, *layout, cast_ptr, value);
let increment = layout.stack_size(env.target_info);
let increment = env.ptr_int().const_int(increment as _, false);
ptr = unsafe {
env.builder.build_gep(ptr, &[increment], "increment_ptr")
};
}
// NOTE: signals to the parent process that an expect failed
if env.mode.runs_expects_in_separate_process() {
let func = env
.module
.get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE)
.unwrap();
bd.build_call(func, &[], "call_expect_finalize_failed");
}
bd.build_unconditional_branch(then_block);
}
roc_target::PtrWidth::Bytes4 => {
// temporary WASM implementation
throw_exception(env, "An expectation failed!");
}
}
} else {
bd.position_at_end(throw_block);
bd.build_unconditional_branch(then_block);
}
bd.position_at_end(then_block);
@ -3538,8 +3654,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
let arguments_for_call = &arguments_for_call.into_bump_slice();
let call_result = match env.mode {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest => {
let call_result = if env.mode.returns_roc_result() {
debug_assert_eq!(args.len(), roc_function.get_params().len());
let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout);
@ -3552,10 +3667,8 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
let wrapped_layout = roc_result_layout(env.arena, return_layout, env.target_info);
call_roc_function(env, roc_function, &wrapped_layout, arguments_for_call)
}
LlvmBackendMode::Binary => {
} else {
call_roc_function(env, roc_function, &return_layout, arguments_for_call)
}
};
let output_arg_index = 0;
@ -3824,7 +3937,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
c_function_name: &str,
) -> FunctionValue<'ctx> {
match env.mode {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest => {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest | LlvmBackendMode::CliTest => {
return expose_function_to_host_help_c_abi_gen_test(
env,
ident_string,
@ -3882,7 +3995,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
debug_info_init!(env, size_function);
let return_type = match env.mode {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest => {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest | LlvmBackendMode::CliTest => {
roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
@ -4630,8 +4743,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
}
}
match env.mode {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest => {
if env.mode.returns_roc_result() {
let call_result = set_jump_and_catch_long_jump(
env,
function_value,
@ -4641,11 +4753,8 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
);
builder.build_store(output, call_result);
}
LlvmBackendMode::Binary => {
let call_result =
call_roc_function(env, evaluator, return_layout, &evaluator_arguments);
} else {
let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments);
if return_layout.is_passed_by_reference(env.target_info) {
let align_bytes = return_layout.alignment_bytes(env.target_info);
@ -4668,7 +4777,6 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
} else {
builder.build_store(output, call_result);
}
}
};
builder.build_return(None);
@ -5342,40 +5450,6 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
_ => unreachable!("invalid list layout"),
}
}
DictWalk { xs, state } => {
let (dict, dict_layout) = load_symbol_and_layout(scope, xs);
let (default, default_layout) = load_symbol_and_layout(scope, state);
let (function, closure, closure_layout) = function_details!();
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let argument_layouts = &[*default_layout, **key_layout, **value_layout];
let roc_function_call = roc_function_call(
env,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
result_layout,
);
dict_walk(
env,
roc_function_call,
dict,
default,
key_layout,
value_layout,
default_layout,
)
}
_ => unreachable!("invalid dict layout"),
}
}
}
}
@ -6151,133 +6225,10 @@ fn run_low_level<'a, 'ctx, 'env>(
BasicValueEnum::IntValue(bool_val)
}
Hash => {
debug_assert_eq!(args.len(), 2);
let seed = load_symbol(scope, &args[0]);
let (value, layout) = load_symbol_and_layout(scope, &args[1]);
debug_assert!(seed.is_int_value());
generic_hash(env, layout_ids, seed.into_int_value(), value, layout).into()
}
DictSize => {
debug_assert_eq!(args.len(), 1);
dict_len(env, scope, args[0])
}
DictGetCapacity => {
// Dict.capacity : Dict * * -> Nat
debug_assert_eq!(args.len(), 1);
let arg = load_symbol(scope, &args[0]);
dict_capacity(env.builder, arg.into_struct_value()).into()
}
DictEmpty => {
debug_assert_eq!(args.len(), 0);
dict_empty(env)
}
DictInsert => {
debug_assert_eq!(args.len(), 3);
let (dict, _) = load_symbol_and_layout(scope, &args[0]);
let (key, key_layout) = load_symbol_and_layout(scope, &args[1]);
let (value, value_layout) = load_symbol_and_layout(scope, &args[2]);
dict_insert(env, layout_ids, dict, key, key_layout, value, value_layout)
}
DictRemove => {
debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let key = load_symbol(scope, &args[1]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_remove(env, layout_ids, dict, key, key_layout, value_layout)
}
DictContains => {
debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let key = load_symbol(scope, &args[1]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_contains(env, layout_ids, dict, key, key_layout, value_layout)
}
DictGetUnsafe => {
debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let key = load_symbol(scope, &args[1]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_get(env, layout_ids, dict, key, key_layout, value_layout)
}
DictKeys => {
debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_keys(env, layout_ids, dict, key_layout, value_layout)
}
DictValues => {
debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_values(env, layout_ids, dict, key_layout, value_layout)
}
DictUnion => {
debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_union(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
DictDifference => {
debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_difference(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
DictIntersection => {
debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_intersection(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
SetFromList => {
debug_assert_eq!(args.len(), 1);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let key_layout = list_element_layout!(list_layout);
set_from_list(env, layout_ids, list, key_layout)
}
SetToDict => {
debug_assert_eq!(args.len(), 1);
let (set, _set_layout) = load_symbol_and_layout(scope, &args[0]);
set
}
SetGetCapacity => {
// Set.capacity : Set * -> Nat
debug_assert_eq!(args.len(), 1);
let arg = load_symbol(scope, &args[0]);
set_capacity(env.builder, arg.into_struct_value()).into()
unimplemented!()
}
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith | DictWalk => {
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => {
unreachable!("these are higher order, and are handled elsewhere")
}
@ -6342,10 +6293,6 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
struct_type.ptr_type(address_space).into()
}
Builtin::Dict(_, _) | Builtin::Set(_) => {
// TODO verify this is what actually happens
basic_type_from_builtin(env, builtin)
}
}
}

View file

@ -59,25 +59,7 @@ pub fn dict_len<'a, 'ctx, 'env>(
let (_, dict_layout) = load_symbol_and_layout(scope, &dict_symbol);
match dict_layout {
Layout::Builtin(Builtin::Dict(_, _)) => {
// let dict_as_int = dict_symbol_to_i128(env, scope, dict_symbol);
let dict_as_zig_dict = dict_symbol_to_zig_dict(env, scope, dict_symbol);
let length_i64 = call_bitcode_fn(
env,
&[pass_dict_c_abi(env, dict_as_zig_dict.into())],
bitcode::DICT_LEN,
);
env.builder
.build_int_cast_sign_flag(
length_i64.into_int_value(),
env.ptr_int(),
false,
"to_usize",
)
.into()
}
Layout::Builtin(Builtin::Dict(_, _)) =>
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout),
}
}

View file

@ -1,874 +0,0 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::tag_pointer_clear_tag_id;
use crate::llvm::build::Env;
use crate::llvm::build::{get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
};
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use super::build::use_roc_value;
use super::convert::argument_type_from_union_layout;
#[derive(Clone, Debug)]
enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),
}
pub fn generic_hash<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) -> IntValue<'ctx> {
// NOTE: C and Zig use this value for their initial HashMap seed: 0xc70f6907
build_hash_layout(
env,
layout_ids,
seed,
val,
layout,
WhenRecursive::Unreachable,
)
}
fn build_hash_layout<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
match layout {
Layout::Builtin(builtin) => {
hash_builtin(env, layout_ids, seed, val, layout, builtin, when_recursive)
}
Layout::Struct { field_layouts, .. } => build_hash_struct(
env,
layout_ids,
field_layouts,
when_recursive,
seed,
val.into_struct_value(),
),
Layout::LambdaSet(lambda_set) => build_hash_layout(
env,
layout_ids,
seed,
val,
&lambda_set.runtime_representation(),
when_recursive,
),
Layout::Union(union_layout) => build_hash_tag(env, layout_ids, union_layout, seed, val),
Layout::Boxed(_inner_layout) => {
// build_hash_box(env, layout_ids, layout, inner_layout, seed, val)
todo!()
}
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout);
let bt = basic_type_from_layout(env, &layout);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(val, bt, "i64_to_opaque")
.into_pointer_value();
build_hash_tag(env, layout_ids, &union_layout, seed, field_cast.into())
}
},
}
}
fn append_hash_layout<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
build_hash_layout(env, layout_ids, seed, val, layout, when_recursive)
}
fn hash_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
builtin: &Builtin<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
let ptr_bytes = env.target_info;
match builtin {
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => {
let hash_bytes = store_and_use_as_u8_ptr(env, val, layout);
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
}
Builtin::Str => {
// let zig deal with big vs small string
call_bitcode_fn(env, &[seed.into(), val], bitcode::DICT_HASH_STR).into_int_value()
}
Builtin::Dict(_, _) => {
todo!("Implement hash for Dict")
}
Builtin::Set(_) => {
todo!("Implement Hash for Set")
}
Builtin::List(element_layout) => build_hash_list(
env,
layout_ids,
layout,
element_layout,
when_recursive,
seed,
val.into_struct_value(),
),
}
}
fn build_hash_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
field_layouts: &'a [Layout<'a>],
when_recursive: WhenRecursive<'a>,
seed: IntValue<'ctx>,
value: StructValue<'ctx>,
) -> IntValue<'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::struct_no_name_order(field_layouts);
let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids
.get(symbol, &struct_layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(env, &struct_layout);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
seed_type.into(),
&[seed_type.into(), arg_type],
);
build_hash_struct_help(
env,
layout_ids,
function_value,
when_recursive,
field_layouts,
);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[seed.into(), value.into()], "struct_hash");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap().into_int_value()
}
fn build_hash_struct_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
when_recursive: WhenRecursive<'a>,
field_layouts: &[Layout<'a>],
) {
let ctx = env.context;
debug_info_init!(env, parent);
// Add args to scope
let mut it = parent.get_param_iter();
let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap().into_struct_value();
seed.set_name(Symbol::ARG_1.as_str(&env.interns));
value.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let result = hash_struct(env, layout_ids, seed, value, when_recursive, field_layouts);
env.builder.build_return(Some(&result));
}
fn hash_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mut seed: IntValue<'ctx>,
value: StructValue<'ctx>,
when_recursive: WhenRecursive<'a>,
field_layouts: &[Layout<'a>],
) -> IntValue<'ctx> {
let ptr_bytes = env.target_info;
let layout = Layout::struct_no_name_order(field_layouts);
// Optimization: if the bit representation of equal values is the same
// just hash the bits. Caveat here is tags: e.g. `Nothing` in `Just a`
// contains garbage bits after the tag (currently)
if false {
// this is a struct of only basic types, so we can just hash its bits
let hash_bytes = store_and_use_as_u8_ptr(env, value.into(), &layout);
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
} else {
for (index, field_layout) in field_layouts.iter().enumerate() {
let field = env
.builder
.build_extract_value(value, index as u32, "hash_field")
.unwrap();
let field = use_roc_value(env, *field_layout, field, "store_field_for_hashing");
if let Layout::RecursivePointer = 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::Union(*union_layout);
let bt = basic_type_from_layout(env, &field_layout);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(field, bt, "i64_to_opaque")
.into_pointer_value();
seed = append_hash_layout(
env,
layout_ids,
seed,
field_cast.into(),
&field_layout,
when_recursive.clone(),
)
}
}
} else {
seed = append_hash_layout(
env,
layout_ids,
seed,
field,
field_layout,
when_recursive.clone(),
);
}
}
seed
}
}
fn build_hash_tag<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
union_layout: &UnionLayout<'a>,
seed: IntValue<'ctx>,
value: BasicValueEnum<'ctx>,
) -> IntValue<'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::GENERIC_HASH;
let fn_name = layout_ids
.get(symbol, &Layout::Union(*union_layout))
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let seed_type = env.context.i64_type();
let arg_type = argument_type_from_union_layout(env, union_layout);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
seed_type.into(),
&[seed_type.into(), arg_type],
);
build_hash_tag_help(env, layout_ids, function_value, union_layout);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[seed.into(), value.into()], "struct_hash");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap().into_int_value()
}
fn build_hash_tag_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
union_layout: &UnionLayout<'a>,
) {
let ctx = env.context;
debug_info_init!(env, parent);
// Add args to scope
let mut it = parent.get_param_iter();
let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap();
seed.set_name(Symbol::ARG_1.as_str(&env.interns));
value.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let result = hash_tag(env, layout_ids, parent, seed, value, union_layout);
env.builder.build_return(Some(&result));
}
fn hash_tag<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
seed: IntValue<'ctx>,
tag: BasicValueEnum<'ctx>,
union_layout: &UnionLayout<'a>,
) -> IntValue<'ctx> {
use UnionLayout::*;
let entry_block = env.builder.get_insert_block().unwrap();
let merge_block = env.context.append_basic_block(parent, "merge_block");
env.builder.position_at_end(merge_block);
let tag_id_layout = union_layout.tag_id_layout();
let tag_id_basic_type = basic_type_from_layout(env, &tag_id_layout);
let merge_phi = env.builder.build_phi(seed.get_type(), "merge_hash");
env.builder.position_at_end(entry_block);
match union_layout {
NonRecursive(tags) => {
let current_tag_id = get_tag_id(env, parent, union_layout, tag);
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
for (tag_id, field_layouts) in tags.iter().enumerate() {
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
// hash the tag id
let hash_bytes = store_and_use_as_u8_ptr(
env,
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false)
.into(),
&tag_id_layout,
);
let seed = hash_bitcode_fn(
env,
seed,
hash_bytes,
tag_id_layout.stack_size(env.target_info),
);
// hash the tag data
let tag = tag.into_pointer_value();
let answer =
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
cases.push((
current_tag_id.get_type().const_int(tag_id as u64, false),
block,
));
}
env.builder.position_at_end(entry_block);
match cases.pop() {
Some((_, default)) => {
env.builder.build_switch(current_tag_id, default, &cases);
}
None => {
// we're hashing empty tag unions; this code is effectively unreachable
env.builder.build_unreachable();
}
}
}
Recursive(tags) => {
let current_tag_id = get_tag_id(env, parent, union_layout, tag);
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
for (tag_id, field_layouts) in tags.iter().enumerate() {
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
// hash the tag id
let hash_bytes = store_and_use_as_u8_ptr(
env,
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false)
.into(),
&tag_id_layout,
);
let seed = hash_bitcode_fn(
env,
seed,
hash_bytes,
tag_id_layout.stack_size(env.target_info),
);
// hash the tag data
let tag = tag_pointer_clear_tag_id(env, tag.into_pointer_value());
let answer =
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
cases.push((
current_tag_id.get_type().const_int(tag_id as u64, false),
block,
));
}
env.builder.position_at_end(entry_block);
let default = cases.pop().unwrap().1;
env.builder.build_switch(current_tag_id, default, &cases);
}
NullableUnwrapped { other_fields, .. } => {
let tag = tag.into_pointer_value();
let is_null = env.builder.build_is_null(tag, "is_null");
let hash_null_block = env.context.append_basic_block(parent, "hash_null_block");
let hash_other_block = env.context.append_basic_block(parent, "hash_other_block");
env.builder
.build_conditional_branch(is_null, hash_null_block, hash_other_block);
{
env.builder.position_at_end(hash_null_block);
let answer = hash_null(seed);
merge_phi.add_incoming(&[(&answer, hash_null_block)]);
env.builder.build_unconditional_branch(merge_block);
}
{
env.builder.position_at_end(hash_other_block);
let answer =
hash_ptr_to_struct(env, layout_ids, union_layout, other_fields, seed, tag);
merge_phi.add_incoming(&[(&answer, hash_other_block)]);
env.builder.build_unconditional_branch(merge_block);
}
}
NullableWrapped {
other_tags,
nullable_id,
} => {
let tag = tag.into_pointer_value();
let is_null = env.builder.build_is_null(tag, "is_null");
let hash_null_block = env.context.append_basic_block(parent, "hash_null_block");
let hash_other_block = env.context.append_basic_block(parent, "hash_other_block");
env.builder
.build_conditional_branch(is_null, hash_null_block, hash_other_block);
{
env.builder.position_at_end(hash_null_block);
let answer = hash_null(seed);
merge_phi.add_incoming(&[(&answer, hash_null_block)]);
env.builder.build_unconditional_branch(merge_block);
}
{
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
for (mut tag_id, field_layouts) in other_tags.iter().enumerate() {
if tag_id >= *nullable_id as usize {
tag_id += 1;
}
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
// hash the tag id
let hash_bytes = store_and_use_as_u8_ptr(
env,
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false)
.into(),
&tag_id_layout,
);
let seed1 = hash_bitcode_fn(
env,
seed,
hash_bytes,
tag_id_layout.stack_size(env.target_info),
);
// hash tag data
let tag = tag_pointer_clear_tag_id(env, tag);
let answer = hash_ptr_to_struct(
env,
layout_ids,
union_layout,
field_layouts,
seed1,
tag,
);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
cases.push((
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false),
block,
));
}
env.builder.position_at_end(hash_other_block);
let tag_id = get_tag_id(env, parent, union_layout, tag.into());
let default = cases.pop().unwrap().1;
env.builder.build_switch(tag_id, default, &cases);
}
}
NonNullableUnwrapped(field_layouts) => {
let answer = hash_ptr_to_struct(
env,
layout_ids,
union_layout,
field_layouts,
seed,
tag.into_pointer_value(),
);
merge_phi.add_incoming(&[(&answer, entry_block)]);
env.builder.build_unconditional_branch(merge_block);
}
}
env.builder.position_at_end(merge_block);
merge_phi.as_basic_value().into_int_value()
}
fn build_hash_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
element_layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>,
seed: IntValue<'ctx>,
value: StructValue<'ctx>,
) -> IntValue<'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::GENERIC_HASH;
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(env, layout);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
seed_type.into(),
&[seed_type.into(), arg_type],
);
build_hash_list_help(
env,
layout_ids,
function_value,
when_recursive,
element_layout,
);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[seed.into(), value.into()], "struct_hash");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap().into_int_value()
}
fn build_hash_list_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
when_recursive: WhenRecursive<'a>,
element_layout: &Layout<'a>,
) {
let ctx = env.context;
debug_info_init!(env, parent);
// Add args to scope
let mut it = parent.get_param_iter();
let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap().into_struct_value();
seed.set_name(Symbol::ARG_1.as_str(&env.interns));
value.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let result = hash_list(
env,
layout_ids,
parent,
seed,
value,
when_recursive,
element_layout,
);
env.builder.build_return(Some(&result));
}
fn hash_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
seed: IntValue<'ctx>,
value: StructValue<'ctx>,
when_recursive: WhenRecursive<'a>,
element_layout: &Layout<'a>,
) -> IntValue<'ctx> {
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
use inkwell::types::BasicType;
// hash of a list is the hash of its elements
let done_block = env.context.append_basic_block(parent, "done");
let loop_block = env.context.append_basic_block(parent, "loop");
let element_type = basic_type_from_layout(env, element_layout);
let ptr_type = element_type.ptr_type(inkwell::AddressSpace::Generic);
let (length, ptr) = load_list(env.builder, value, ptr_type);
let result = env.builder.build_alloca(env.context.i64_type(), "result");
env.builder.build_store(result, seed);
let is_empty = env.builder.build_int_compare(
inkwell::IntPredicate::EQ,
length,
env.ptr_int().const_zero(),
"is_empty",
);
env.builder
.build_conditional_branch(is_empty, done_block, loop_block);
env.builder.position_at_end(loop_block);
let loop_fn = |_index, element| {
let seed = env
.builder
.build_load(result, "load_current")
.into_int_value();
let answer = append_hash_layout(
env,
layout_ids,
seed,
element,
element_layout,
when_recursive.clone(),
);
env.builder.build_store(result, answer);
};
incrementing_elem_loop(
env,
parent,
*element_layout,
ptr,
length,
"current_index",
loop_fn,
);
env.builder.build_unconditional_branch(done_block);
env.builder.position_at_end(done_block);
env.builder
.build_load(result, "load_current")
.into_int_value()
}
fn hash_null(seed: IntValue<'_>) -> IntValue<'_> {
seed
}
fn hash_ptr_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
union_layout: &UnionLayout<'a>,
field_layouts: &'a [Layout<'a>],
seed: IntValue<'ctx>,
tag: PointerValue<'ctx>,
) -> IntValue<'ctx> {
use inkwell::types::BasicType;
let wrapper_type = argument_type_from_union_layout(env, union_layout);
// cast the opaque pointer to a pointer of the correct shape
let wrapper_ptr = env
.builder
.build_bitcast(tag, wrapper_type, "hash_ptr_to_struct_opaque_to_correct")
.into_pointer_value();
let struct_ptr = env
.builder
.build_struct_gep(wrapper_ptr, TAG_DATA_INDEX, "get_tag_data")
.unwrap();
let struct_layout = Layout::struct_no_name_order(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout);
let struct_ptr = env
.builder
.build_bitcast(
struct_ptr,
struct_type.ptr_type(inkwell::AddressSpace::Generic),
"cast_tag_data",
)
.into_pointer_value();
let struct_value = env
.builder
.build_load(struct_ptr, "load_struct1")
.into_struct_value();
build_hash_struct(
env,
layout_ids,
field_layouts,
WhenRecursive::Loop(*union_layout),
seed,
struct_value,
)
}
fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) -> PointerValue<'ctx> {
let basic_type = basic_type_from_layout(env, layout);
let alloc = env.builder.build_alloca(basic_type, "store");
env.builder.build_store(alloc, value);
let u8_ptr = env
.context
.i8_type()
.ptr_type(inkwell::AddressSpace::Generic);
env.builder
.build_bitcast(alloc, u8_ptr, "as_u8_ptr")
.into_pointer_value()
}
fn hash_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
seed: IntValue<'ctx>,
buffer: PointerValue<'ctx>,
width: u32,
) -> IntValue<'ctx> {
let num_bytes = env.ptr_int().const_int(width as u64, false);
call_bitcode_fn(
env,
&[seed.into(), buffer.into(), num_bytes.into()],
bitcode::DICT_HASH,
)
.into_int_value()
}

View file

@ -135,8 +135,6 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
rhs_val.into_struct_value(),
when_recursive,
),
Builtin::Set(_elem) => todo!("equality on Set"),
Builtin::Dict(_key, _value) => todo!("equality on Dict"),
}
}
@ -312,8 +310,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
result.into()
}
Builtin::Set(_elem) => todo!("equality on Set"),
Builtin::Dict(_key, _value) => todo!("equality on Dict"),
}
}

View file

@ -103,8 +103,6 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
Float(float_width) => float_type_from_float_width(env, *float_width).as_basic_type_enum(),
Bool => context.bool_type().as_basic_type_enum(),
Decimal => context.i128_type().as_basic_type_enum(),
Dict(_, _) => zig_dict_type(env).into(),
Set(_) => zig_dict_type(env).into(),
List(_) => zig_list_type(env).into(),
Str => zig_str_type(env).into(),
}
@ -260,10 +258,6 @@ pub fn str_list_int(ctx: &Context, target_info: TargetInfo) -> IntType<'_> {
}
}
pub fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
env.module.get_struct_type("dict.RocDict").unwrap()
}
pub fn zig_list_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
env.module.get_struct_type("list.RocList").unwrap()
}

View file

@ -7,7 +7,7 @@ use inkwell::values::BasicValue;
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use super::build::{get_sjlj_buffer, LlvmBackendMode, LLVM_LONGJMP};
use super::build::{get_sjlj_buffer, LLVM_LONGJMP};
/// Define functions for roc_alloc, roc_realloc, and roc_dealloc
/// which use libc implementations (malloc, realloc, and free)
@ -19,10 +19,7 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
let usize_type = env.ptr_int();
let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
match env.mode {
LlvmBackendMode::Binary => { /* externs are provided by the platform */ }
LlvmBackendMode::WasmGenTest => { /* externs are provided by the wasm test platform */ }
LlvmBackendMode::GenTest => {
if !env.mode.has_host() {
// roc_alloc
{
// The type of this function (but not the implementation) should have
@ -65,8 +62,7 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
usize_type.into(),
],
);
let fn_val =
add_func(env.context, module, "realloc", fn_spec, Linkage::External);
let fn_val = add_func(env.context, module, "realloc", fn_spec, Linkage::External);
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
@ -147,7 +143,6 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
add_sjlj_roc_panic(env)
}
}
}
pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
let ctx = env.context;

View file

@ -1,7 +1,5 @@
pub mod bitcode;
pub mod build;
pub mod build_dict;
pub mod build_hash;
pub mod build_list;
pub mod build_str;
pub mod compare;

View file

@ -423,35 +423,6 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
Some(function)
}
Set(element_layout) => {
let key_layout = element_layout;
let value_layout = &Layout::UNIT;
let function = modify_refcount_dict(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
);
Some(function)
}
Dict(key_layout, value_layout) => {
let function = modify_refcount_dict(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
);
Some(function)
}
Str => Some(modify_refcount_str(env, layout_ids, mode, layout)),
@ -960,141 +931,6 @@ fn modify_refcount_box_help<'a, 'ctx, 'env>(
builder.build_return(None);
}
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'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 (_, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_dict",
"decrement_dict",
layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let basic_type = basic_type_from_layout(env, layout);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_dict_help(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
function_value,
);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function
}
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
fn_val: FunctionValue<'ctx>,
) {
debug_assert_eq!(
when_recursive,
&WhenRecursive::Unreachable,
"TODO pipe when_recursive through the dict key/value inc/dec"
);
let builder = env.builder;
let ctx = env.context;
// Add a basic block for the entry point
let entry = ctx.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
debug_info_init!(env, fn_val);
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
arg_val.set_name(arg_symbol.as_str(&env.interns));
let parent = fn_val;
let wrapper_struct = arg_val.into_struct_value();
let len = builder
.build_extract_value(wrapper_struct, 1, "read_dict_len")
.unwrap()
.into_int_value();
// the block we'll always jump to when we're done
let cont_block = ctx.append_basic_block(parent, "modify_rc_dict_cont");
let modification_block = ctx.append_basic_block(parent, "modify_rc");
let is_non_empty = builder.build_int_compare(
IntPredicate::SGT,
len,
env.ptr_int().const_zero(),
"is_non_empty",
);
builder.build_conditional_branch(is_non_empty, modification_block, cont_block);
builder.position_at_end(modification_block);
if key_layout.contains_refcounted() || value_layout.contains_refcounted() {
crate::llvm::build_dict::dict_elements_rc(
env,
layout_ids,
wrapper_struct.into(),
key_layout,
value_layout,
mode,
);
}
let data_ptr = env
.builder
.build_extract_value(wrapper_struct, 0, "get_data_ptr")
.unwrap()
.into_pointer_value();
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, data_ptr);
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env);
builder.build_unconditional_branch(cont_block);
builder.position_at_end(cont_block);
// this function returns void
builder.build_return(None);
}
/// Build an increment or decrement function for a specific layout
fn build_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -18,7 +18,7 @@ pub enum ReturnMethod {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StackMemoryFormat {
/// Record, Str, List, Dict, etc.
/// Record, Str, List, etc.
DataStructure,
Int128,
Float128,
@ -87,7 +87,7 @@ impl WasmLayout {
Layout::LambdaSet(lambda_set) => WasmLayout::new(&lambda_set.runtime_representation()),
Layout::Builtin(Str | Dict(_, _) | Set(_) | List(_))
Layout::Builtin(Str | List(_))
| Layout::Struct { .. }
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
size,

View file

@ -346,7 +346,7 @@ impl<'a> LowLevelCall<'a> {
ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE),
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith | DictWalk => {
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => {
internal_error!("HigherOrder lowlevels should not be handled here")
}
@ -775,12 +775,6 @@ impl<'a> LowLevelCall<'a> {
backend.call_host_fn_after_loading_args(bitcode::LIST_SWAP, 8, false);
}
DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe
| DictKeys | DictValues | DictUnion | DictIntersection | DictDifference
| SetFromList | SetToDict => {
todo!("{:?}", self.lowlevel);
}
// Num
NumAdd => match self.ret_layout {
Layout::Builtin(Builtin::Int(width)) => {
@ -1812,46 +1806,6 @@ impl<'a> LowLevelCall<'a> {
unreachable!("The {:?} operation is turned into mono Expr", self.lowlevel)
}
DictGetCapacity => 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);
// Dict 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 Dict"),
},
SetGetCapacity => 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);
// Set 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 Dict"),
},
Unreachable => match self.ret_storage {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
@ -1901,7 +1855,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i32_const(!invert_result as i32);
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_))
Layout::Builtin(Builtin::List(_))
| Layout::Struct { .. }
| Layout::Union(_)
| Layout::LambdaSet(_)
@ -2153,7 +2107,6 @@ pub fn call_higher_order_lowlevel<'a>(
ListMap { .. } | ListMap2 { .. } | ListMap3 { .. } | ListMap4 { .. } => {
ProcSource::HigherOrderMapper(passed_proc_index)
}
DictWalk { .. } => todo!("DictWalk"),
}
};
let wrapper_sym = backend.create_symbol(&format!("#wrap#{:?}", fn_name));
@ -2301,8 +2254,6 @@ pub fn call_higher_order_lowlevel<'a>(
backend.call_host_fn_after_loading_args(bitcode::LIST_SORT_WITH, 9, false);
}
DictWalk { .. } => todo!("{:?}", op),
}
}

View file

@ -64,7 +64,9 @@ impl<'a> WasmModule<'a> {
self.types.serialize(buffer);
self.import.serialize(buffer);
self.function.serialize(buffer);
if !self.element.is_empty() {
self.table.serialize(buffer);
}
self.memory.serialize(buffer);
self.global.serialize(buffer);
self.export.serialize(buffer);

View file

@ -1118,6 +1118,10 @@ impl<'a> ElementSection<'a> {
pub fn size(&self) -> usize {
self.segments.iter().map(|seg| seg.size()).sum()
}
pub fn is_empty(&self) -> bool {
self.segments.iter().all(|seg| seg.fn_indices.is_empty())
}
}
impl<'a> Parse<&'a Bump> for ElementSection<'a> {
@ -1148,11 +1152,13 @@ impl<'a> Parse<&'a Bump> for ElementSection<'a> {
impl<'a> Serialize for ElementSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if !self.is_empty() {
let header_indices = write_section_header(buffer, Self::ID);
self.segments.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
}
/*******************************************************************
*

View file

@ -8,9 +8,10 @@ edition = "2021"
[dependencies]
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_derive_key = { path = "../derive_key" }
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" }
bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -5,11 +5,13 @@ use std::sync::{Arc, RwLock};
use bumpalo::Bump;
use roc_can::abilities::AbilitiesStore;
use roc_can::module::ExposedByModule;
use roc_collections::MutMap;
use roc_derive_key::GlobalDerivedSymbols;
use roc_derive::SharedDerivedModule;
use roc_error_macros::internal_error;
use roc_module::symbol::ModuleId;
use roc_solve::solve::{compact_lambda_sets_of_vars, Phase, Pools};
use roc_types::subs::{Content, FlatType, LambdaSet};
use roc_types::subs::{get_member_lambda_sets_at_region, Content, FlatType, LambdaSet};
use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable};
use roc_unify::unify::{unify as unify_unify, Mode, Unified};
@ -45,6 +47,30 @@ impl WorldAbilities {
debug_assert!(old_store.is_none(), "{:?} abilities not new", module);
}
#[inline(always)]
pub fn with_module_exposed_type<T>(
&mut self,
module: ModuleId,
mut f: impl FnMut(&mut ExposedTypesStorageSubs) -> T,
) -> T {
let mut world = self.world.write().unwrap();
let (_, exposed_types) = world.get_mut(&module).expect("module not in the world");
f(exposed_types)
}
#[inline(always)]
pub fn with_module_abilities_store<T, F>(&self, module: ModuleId, mut f: F) -> T
where
F: FnMut(&AbilitiesStore) -> T,
{
let world = self.world.read().unwrap();
let (module_store, _module_types) = world
.get(&module)
.unwrap_or_else(|| internal_error!("Module {:?} not found in world abilities", module));
f(module_store)
}
pub fn clone_ref(&self) -> Self {
Self {
world: Arc::clone(&self.world),
@ -53,7 +79,7 @@ impl WorldAbilities {
}
pub enum AbilitiesView<'a> {
World(WorldAbilities),
World(&'a WorldAbilities),
Module(&'a AbilitiesStore),
}
@ -64,11 +90,7 @@ impl AbilitiesView<'_> {
F: FnMut(&AbilitiesStore) -> T,
{
match self {
AbilitiesView::World(wa) => {
let world = wa.world.read().unwrap();
let (module_store, _module_types) = world.get(&module).unwrap();
f(module_store)
}
AbilitiesView::World(wa) => wa.with_module_abilities_store(module, f),
AbilitiesView::Module(store) => f(store),
}
}
@ -138,20 +160,108 @@ impl Phase for LatePhase<'_> {
}
}
}
#[inline(always)]
fn get_and_copy_ability_member_ambient_function(
&self,
ability_member: roc_module::symbol::Symbol,
region: u8,
target_subs: &mut Subs,
) -> Variable {
match self.abilities {
AbilitiesView::Module(abilities_store) => {
// No option other than that the var must be in our module store.
// Even if the specialization lambda set comes from another module,
// we should have taken care to import it before starting solving in this module.
let member_def = abilities_store
.member_def(ability_member)
.unwrap_or_else(|| {
internal_error!(
"{:?} is not resolved, or not an ability member!",
ability_member
)
});
let member_var = member_def.signature_var();
let region_lset = get_member_lambda_sets_at_region(target_subs, member_var, region);
let LambdaSet {
ambient_function, ..
} = target_subs.get_lambda_set(region_lset);
ambient_function
}
AbilitiesView::World(wa) => {
let member_home = ability_member.module_id();
let mut world = wa.world.write().unwrap();
let (module_store, module_types) = world.get_mut(&member_home).unwrap();
let member_def = module_store.member_def(ability_member).unwrap_or_else(|| {
internal_error!(
"{:?} is not resolved, or not an ability member!",
ability_member
)
});
let member_var = member_def.signature_var();
let storage_member_var = module_types
.stored_ability_member_vars
.get(&member_var)
.unwrap();
let storage_lambda_set_var = get_member_lambda_sets_at_region(
module_types.storage_subs.as_inner(),
*storage_member_var,
region,
);
let LambdaSet {
ambient_function, ..
} = module_types
.storage_subs
.as_inner()
.get_lambda_set(storage_lambda_set_var);
let copied = module_types
.storage_subs
// TODO: I think this is always okay, but revisit later when we're in a more
// stable position to see if we can get rid of the bookkeeping done as a result
// of this.
.export_variable_to_directly_to_use_site(target_subs, ambient_function);
let our_ambient_function_var = copied.variable;
instantiate_rigids(target_subs, our_ambient_function_var);
debug_assert!(matches!(
target_subs.get_content_without_compacting(our_ambient_function_var),
Content::Structure(FlatType::Func(..))
));
our_ambient_function_var
}
}
}
}
/// Unifies two variables and performs lambda set compaction.
/// Ranks and other ability demands are disregarded.
#[allow(clippy::too_many_arguments)]
pub fn unify(
home: ModuleId,
arena: &Bump,
subs: &mut Subs,
abilities: &AbilitiesView,
derived_symbols: &GlobalDerivedSymbols,
derived_module: &SharedDerivedModule,
exposed_by_module: &ExposedByModule,
left: Variable,
right: Variable,
) -> Result<(), UnificationFailed> {
debug_assert_ne!(
home,
ModuleId::DERIVED_SYNTH,
"derived module can only unify its subs in its own context!"
);
let unified = unify_unify(subs, left, right, Mode::EQ);
match unified {
Unified::Success {
vars: _,
@ -165,11 +275,12 @@ pub fn unify(
let must_implement_constraints = compact_lambda_sets_of_vars(
subs,
derived_module,
arena,
&mut pools,
lambda_sets_to_specialize,
&late_phase,
derived_symbols,
exposed_by_module,
);
// At this point we can't do anything with must-implement constraints, since we're no
// longer solving. We must assume that they were totally caught during solving.

View file

@ -11,7 +11,7 @@ use std::path::PathBuf;
pub use roc_load_internal::docs;
pub use roc_load_internal::file::{
LoadResult, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase,
Expectations, LoadResult, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase,
};
#[allow(clippy::too_many_arguments)]

View file

@ -14,6 +14,7 @@ roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_constrain = { path = "../constrain" }
roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive" }
roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" }
roc_unify = { path = "../unify" }

View file

@ -20,7 +20,7 @@ use roc_debug_flags::{
ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION,
ROC_PRINT_LOAD_LOG,
};
use roc_derive_key::GlobalDerivedSymbols;
use roc_derive::SharedDerivedModule;
use roc_error_macros::internal_error;
use roc_late_solve::{AbilitiesView, WorldAbilities};
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
@ -131,6 +131,7 @@ struct ModuleCache<'a> {
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
late_specializations: MutMap<ModuleId, LateSpecializationsModule<'a>>,
external_specializations_requested: MutMap<ModuleId, Vec<ExternalSpecializations<'a>>>,
expectations: VecMap<ModuleId, Expectations>,
/// Various information
imports: MutMap<ModuleId, MutSet<ModuleId>>,
@ -185,6 +186,7 @@ impl Default for ModuleCache<'_> {
can_problems: Default::default(),
type_problems: Default::default(),
sources: Default::default(),
expectations: Default::default(),
}
}
}
@ -370,7 +372,7 @@ fn start_phase<'a>(
..
} = constrained;
let derived_symbols = GlobalDerivedSymbols::clone(&state.derived_symbols);
let derived_module = SharedDerivedModule::clone(&state.derived_module);
BuildTask::solve_module(
module,
@ -385,7 +387,7 @@ fn start_phase<'a>(
dep_idents,
declarations,
state.cached_subs.clone(),
derived_symbols,
derived_module,
)
}
Phase::FindSpecializations => {
@ -413,7 +415,7 @@ fn start_phase<'a>(
}
}
let derived_symbols = GlobalDerivedSymbols::clone(&state.derived_symbols);
let derived_module = SharedDerivedModule::clone(&state.derived_module);
BuildTask::BuildPendingSpecializations {
layout_cache,
@ -425,18 +427,44 @@ fn start_phase<'a>(
ident_ids,
exposed_to_host: state.exposed_to_host.clone(),
abilities_store,
derived_symbols,
// TODO: awful, how can we get rid of the clone?
exposed_by_module: state.exposed_types.clone(),
derived_module,
}
}
Phase::MakeSpecializations => {
let specializations_we_must_make = state
let mut specializations_we_must_make = state
.module_cache
.external_specializations_requested
.remove(&module_id)
.unwrap_or_default();
let (ident_ids, subs, procs_base, layout_cache, module_timing) =
if state.make_specializations_pass.current_pass() == 1 {
if module_id == ModuleId::DERIVED_GEN {
// The derived gen module must also fulfill also specializations asked of the
// derived synth module.
let derived_synth_specializations = state
.module_cache
.external_specializations_requested
.remove(&ModuleId::DERIVED_SYNTH)
.unwrap_or_default();
specializations_we_must_make.extend(derived_synth_specializations)
}
let (mut ident_ids, mut subs, mut procs_base, layout_cache, mut module_timing) =
if state.make_specializations_pass.current_pass() == 1
&& module_id == ModuleId::DERIVED_GEN
{
// This is the first time the derived module is introduced into the load
// graph. It has no abilities of its own or anything, just generate fresh
// information for it.
(
IdentIds::default(),
Subs::default(),
ProcsBase::default(),
LayoutCache::new(state.target_info),
ModuleTiming::new(SystemTime::now()),
)
} else if state.make_specializations_pass.current_pass() == 1 {
let found_specializations = state
.module_cache
.found_specializations
@ -452,12 +480,13 @@ fn start_phase<'a>(
abilities_store,
} = found_specializations;
// Safety: by this point every module should have been solved, so there is no need
// for our exposed types anymore, but the world does need them.
let our_exposed_types = unsafe { state.exposed_types.remove(&module_id) }
let our_exposed_types = state
.exposed_types
.get(&module_id)
.unwrap_or_else(|| {
internal_error!("Exposed types for {:?} missing", module_id)
});
})
.clone();
// Add our abilities to the world.
state.world_abilities.insert(
@ -483,7 +512,22 @@ fn start_phase<'a>(
(ident_ids, subs, procs_base, layout_cache, module_timing)
};
let derived_symbols = GlobalDerivedSymbols::clone(&state.derived_symbols);
if module_id == ModuleId::DERIVED_GEN {
load_derived_partial_procs(
module_id,
arena,
&mut subs,
&mut ident_ids,
&state.derived_module,
&mut module_timing,
state.target_info,
&state.exposed_types,
&mut procs_base,
&mut state.world_abilities,
);
}
let derived_module = SharedDerivedModule::clone(&state.derived_module);
BuildTask::MakeSpecializations {
module_id,
@ -494,7 +538,9 @@ fn start_phase<'a>(
specializations_we_must_make,
module_timing,
world_abilities: state.world_abilities.clone_ref(),
derived_symbols,
// TODO: awful, how can we get rid of the clone?
exposed_by_module: state.exposed_types.clone(),
derived_module,
}
}
}
@ -631,6 +677,15 @@ pub struct MonomorphizedModule<'a> {
pub exposed_to_host: ExposedToHost,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
pub expectations: VecMap<ModuleId, Expectations>,
}
#[derive(Debug)]
pub struct Expectations {
pub subs: roc_types::subs::Subs,
pub path: PathBuf,
pub expectations: VecMap<Region, Vec<(Symbol, Variable)>>,
pub ident_ids: IdentIds,
}
#[derive(Clone, Debug, Default)]
@ -673,6 +728,8 @@ struct ParsedModule<'a> {
header_for: HeaderFor<'a>,
}
type LocExpects = VecMap<Region, Vec<(Symbol, Variable)>>;
/// A message sent out _from_ a worker thread,
/// representing a result of work done, or a request for further work
#[derive(Debug)]
@ -690,6 +747,7 @@ enum Msg<'a> {
dep_idents: IdentIdsByModule,
module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
loc_expects: LocExpects,
},
FinishedAllTypeChecking {
solved_subs: Solved<Subs>,
@ -808,7 +866,7 @@ struct State<'a> {
pub arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
pub arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
#[allow(unused)]
pub derived_symbols: GlobalDerivedSymbols,
pub derived_module: SharedDerivedModule,
pub ident_ids_by_module: SharedIdentIdsByModule,
@ -853,6 +911,8 @@ impl<'a> State<'a> {
) -> Self {
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
let dependencies = Dependencies::new(goal_phase);
Self {
root_id,
root_subs: None,
@ -862,14 +922,14 @@ impl<'a> State<'a> {
output_path: None,
platform_path: PlatformPath::NotSpecified,
module_cache: ModuleCache::default(),
dependencies: Dependencies::default(),
dependencies,
procedures: MutMap::default(),
toplevel_expects: Vec::new(),
exposed_to_host: ExposedToHost::default(),
exposed_types,
arc_modules,
arc_shorthands,
derived_symbols: Default::default(),
derived_module: Default::default(),
constrained_ident_ids: IdentIds::exposed_builtins(0),
ident_ids_by_module,
declarations_by_id: MutMap::default(),
@ -988,7 +1048,7 @@ enum BuildTask<'a> {
declarations: Declarations,
dep_idents: IdentIdsByModule,
cached_subs: CachedSubs,
derived_symbols: GlobalDerivedSymbols,
derived_module: SharedDerivedModule,
},
BuildPendingSpecializations {
module_timing: ModuleTiming,
@ -999,8 +1059,9 @@ enum BuildTask<'a> {
ident_ids: IdentIds,
decls: Declarations,
exposed_to_host: ExposedToHost,
exposed_by_module: ExposedByModule,
abilities_store: AbilitiesStore,
derived_symbols: GlobalDerivedSymbols,
derived_module: SharedDerivedModule,
},
MakeSpecializations {
module_id: ModuleId,
@ -1010,8 +1071,9 @@ enum BuildTask<'a> {
layout_cache: LayoutCache<'a>,
specializations_we_must_make: Vec<ExternalSpecializations<'a>>,
module_timing: ModuleTiming,
exposed_by_module: ExposedByModule,
world_abilities: WorldAbilities,
derived_symbols: GlobalDerivedSymbols,
derived_module: SharedDerivedModule,
},
}
@ -2080,6 +2142,10 @@ fn update<'a>(
.imported_modules
.insert(ModuleId::DICT, Region::zero());
header
.exposed_imports
.insert(Ident::from("Dict"), (Symbol::DICT_DICT, Region::zero()));
header
.package_qualified_imported_modules
.insert(PackageQualified::Unqualified(ModuleId::SET));
@ -2088,6 +2154,10 @@ fn update<'a>(
.imported_modules
.insert(ModuleId::SET, Region::zero());
header
.exposed_imports
.insert(Ident::from("Set"), (Symbol::SET_SET, Region::zero()));
header
.package_qualified_imported_modules
.insert(PackageQualified::Unqualified(ModuleId::LIST));
@ -2210,6 +2280,7 @@ fn update<'a>(
dep_idents,
mut module_timing,
abilities_store,
loc_expects,
} => {
log!("solved types for {:?}", module_id);
module_timing.end_time = SystemTime::now();
@ -2219,6 +2290,22 @@ fn update<'a>(
.type_problems
.insert(module_id, solved_module.problems);
if !loc_expects.is_empty() {
let (path, _) = state.module_cache.sources.get(&module_id).unwrap();
let expectations = Expectations {
expectations: loc_expects,
subs: solved_subs.clone().into_inner(),
path: path.to_owned(),
ident_ids: ident_ids.clone(),
};
state
.module_cache
.expectations
.insert(module_id, expectations);
}
let work = state.dependencies.notify(module_id, Phase::SolveTypes);
// if there is a platform, the `platform` module provides host-exposed,
@ -2629,7 +2716,16 @@ fn finish_specialization(
.into_inner()
.into_module_ids();
let all_ident_ids = state.constrained_ident_ids;
let mut all_ident_ids = state.constrained_ident_ids;
// Associate the ident IDs from the derived synth module
let (_, derived_synth_ident_ids) = Arc::try_unwrap(state.derived_module)
.unwrap_or_else(|_| internal_error!("Outstanding references to the derived module"))
.into_inner()
.unwrap()
.decompose();
ModuleId::DERIVED_SYNTH.register_debug_idents(&derived_synth_ident_ids);
all_ident_ids.insert(ModuleId::DERIVED_SYNTH, derived_synth_ident_ids);
let interns = Interns {
module_ids,
@ -2647,6 +2743,7 @@ fn finish_specialization(
} = state;
let ModuleCache {
expectations,
type_problems,
can_problems,
sources,
@ -2727,6 +2824,7 @@ fn finish_specialization(
sources,
timings: state.timings,
toplevel_expects,
expectations,
})
}
@ -2747,12 +2845,16 @@ fn finish(
.into_inner()
.into_module_ids();
// Steal the derived symbols and put them in the global ident ids
let derived_ident_ids = state.derived_symbols.lock().unwrap().steal();
ModuleId::DERIVED.register_debug_idents(&derived_ident_ids);
// Associate the ident IDs from the derived synth module
let (_, derived_synth_ident_ids) = Arc::try_unwrap(state.derived_module)
.unwrap_or_else(|_| internal_error!("Outstanding references to the derived module"))
.into_inner()
.unwrap()
.decompose();
ModuleId::DERIVED_SYNTH.register_debug_idents(&derived_synth_ident_ids);
state
.constrained_ident_ids
.insert(ModuleId::DERIVED, derived_ident_ids);
.insert(ModuleId::DERIVED_SYNTH, derived_synth_ident_ids);
let interns = Interns {
module_ids,
@ -3797,7 +3899,7 @@ impl<'a> BuildTask<'a> {
dep_idents: IdentIdsByModule,
declarations: Declarations,
cached_subs: CachedSubs,
derived_symbols: GlobalDerivedSymbols,
derived_module: SharedDerivedModule,
) -> Self {
let exposed_by_module = exposed_types.retain_modules(imported_modules.keys());
@ -3817,7 +3919,7 @@ impl<'a> BuildTask<'a> {
dep_idents,
module_timing,
cached_subs,
derived_symbols,
derived_module,
}
}
}
@ -3865,7 +3967,7 @@ pub fn add_imports(
my_module: ModuleId,
subs: &mut Subs,
mut pending_abilities: PendingAbilitiesStore,
mut exposed_for_module: ExposedForModule,
exposed_for_module: &ExposedForModule,
def_types: &mut Vec<(Symbol, Loc<roc_types::types::Type>)>,
rigid_vars: &mut Vec<Variable>,
) -> (Vec<Variable>, AbilitiesStore) {
@ -3878,7 +3980,7 @@ pub fn add_imports(
macro_rules! import_var_for_symbol {
($subs:expr, $exposed_by_module:expr, $symbol:ident, $break:stmt) => {
let module_id = $symbol.module_id();
match $exposed_by_module.get_mut(&module_id) {
match $exposed_by_module.get(&module_id) {
Some(ExposedModuleTypes {
exposed_types_storage_subs: exposed_types,
resolved_specializations: _,
@ -3921,7 +4023,7 @@ pub fn add_imports(
}
}
for symbol in exposed_for_module.imported_values {
for &symbol in &exposed_for_module.imported_values {
import_var_for_symbol!(subs, exposed_for_module.exposed_by_module, symbol, continue);
}
@ -3947,14 +4049,14 @@ pub fn add_imports(
struct Ctx<'a> {
subs: &'a mut Subs,
exposed_by_module: &'a mut ExposedByModule,
exposed_by_module: &'a ExposedByModule,
}
let abilities_store = pending_abilities.resolve_for_module(
my_module,
&mut Ctx {
subs,
exposed_by_module: &mut exposed_for_module.exposed_by_module,
exposed_by_module: &exposed_for_module.exposed_by_module,
},
|ctx, symbol| match cached_symbol_vars.get(&symbol).copied() {
Some(var) => var,
@ -3968,7 +4070,7 @@ pub fn add_imports(
*cached_symbol_vars.get(&symbol).unwrap()
}
},
|ctx, module, lset_var| match ctx.exposed_by_module.get_mut(&module) {
|ctx, module, lset_var| match ctx.exposed_by_module.get(&module) {
Some(ExposedModuleTypes {
exposed_types_storage_subs: exposed_types,
resolved_specializations: _,
@ -3999,7 +4101,7 @@ fn run_solve_solve(
pending_derives: PendingDerives,
var_store: VarStore,
module: Module,
derived_symbols: GlobalDerivedSymbols,
derived_module: SharedDerivedModule,
) -> (
Solved<Subs>,
ResolvedSpecializations,
@ -4024,7 +4126,7 @@ fn run_solve_solve(
module.module_id,
&mut subs,
pending_abilities,
exposed_for_module,
&exposed_for_module,
&mut def_types,
&mut rigid_vars,
);
@ -4039,7 +4141,10 @@ fn run_solve_solve(
}
let (solved_subs, solved_specializations, exposed_vars_by_symbol, problems, abilities_store) = {
let module_id = module.module_id;
let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve(
module_id,
&constraints,
actual_constraint,
rigid_variables,
@ -4047,10 +4152,10 @@ fn run_solve_solve(
solve_aliases,
abilities_store,
pending_derives,
derived_symbols,
&exposed_for_module.exposed_by_module,
derived_module,
);
let module_id = module.module_id;
// Figure out what specializations belong to this module
let solved_specializations: ResolvedSpecializations = abilities_store
.iter_specializations()
@ -4103,7 +4208,7 @@ fn run_solve<'a>(
decls: Declarations,
dep_idents: IdentIdsByModule,
cached_subs: CachedSubs,
derived_symbols: GlobalDerivedSymbols,
derived_module: SharedDerivedModule,
) -> Msg<'a> {
let solve_start = SystemTime::now();
@ -4112,6 +4217,10 @@ fn run_solve<'a>(
// TODO remove when we write builtins in roc
let aliases = module.aliases.clone();
let mut module = module;
let loc_expects = std::mem::take(&mut module.loc_expects);
let module = module;
let (solved_subs, solved_specializations, exposed_vars_by_symbol, problems, abilities_store) = {
if module_id.is_builtin() {
match cached_subs.lock().remove(&module_id) {
@ -4122,7 +4231,7 @@ fn run_solve<'a>(
pending_derives,
var_store,
module,
derived_symbols,
derived_module,
),
Some((subs, exposed_vars_by_symbol)) => {
(
@ -4144,16 +4253,18 @@ fn run_solve<'a>(
pending_derives,
var_store,
module,
derived_symbols,
derived_module,
)
}
};
let mut solved_subs = solved_subs;
let exposed_types = roc_solve::module::exposed_types_storage_subs(
module_id,
&mut solved_subs,
&exposed_vars_by_symbol,
&solved_specializations,
&abilities_store,
);
let solved_module = SolvedModule {
@ -4178,6 +4289,7 @@ fn run_solve<'a>(
solved_module,
module_timing,
abilities_store,
loc_expects,
}
}
@ -4302,8 +4414,10 @@ fn canonicalize_and_constrain<'a>(
ModuleNameEnum::Platform => None,
ModuleNameEnum::App(_) => None,
ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => {
let mut scope = module_output.scope.clone();
scope.add_docs_imports();
let docs = crate::docs::generate_module_docs(
module_output.scope.clone(),
scope,
name.as_str().into(),
&parsed_defs_for_docs,
);
@ -4347,13 +4461,19 @@ fn canonicalize_and_constrain<'a>(
.into_iter()
.map(|(k, v)| (k, (true, v)))
.collect();
for (name, alias) in module_output.scope.aliases {
match aliases.entry(name) {
Occupied(_) => {
// do nothing
}
Vacant(vacant) => {
if !name.is_builtin() || name.module_id() == ModuleId::ENCODE {
let should_include_builtin = matches!(
name.module_id(),
ModuleId::ENCODE | ModuleId::DICT | ModuleId::SET
);
if !name.is_builtin() || should_include_builtin {
vacant.insert((false, alias));
}
}
@ -4369,6 +4489,7 @@ fn canonicalize_and_constrain<'a>(
aliases,
rigid_variables: module_output.rigid_variables,
abilities_store: module_output.scope.abilities_store,
loc_expects: module_output.loc_expects,
};
let constrained_module = ConstrainedModule {
@ -4500,7 +4621,8 @@ fn make_specializations<'a>(
mut module_timing: ModuleTiming,
target_info: TargetInfo,
world_abilities: WorldAbilities,
derived_symbols: GlobalDerivedSymbols,
exposed_by_module: &ExposedByModule,
derived_module: SharedDerivedModule,
) -> Msg<'a> {
let make_specializations_start = SystemTime::now();
let mut update_mode_ids = UpdateModeIds::new();
@ -4514,8 +4636,9 @@ fn make_specializations<'a>(
update_mode_ids: &mut update_mode_ids,
// call_specialization_counter=0 is reserved
call_specialization_counter: 1,
abilities: AbilitiesView::World(world_abilities),
derived_symbols: &derived_symbols,
abilities: AbilitiesView::World(&world_abilities),
exposed_by_module,
derived_module: &derived_module,
};
let mut procs = Procs::new_in(arena);
@ -4577,8 +4700,9 @@ fn build_pending_specializations<'a>(
mut layout_cache: LayoutCache<'a>,
target_info: TargetInfo,
exposed_to_host: ExposedToHost,
exposed_by_module: &ExposedByModule,
abilities_store: AbilitiesStore,
derived_symbols: GlobalDerivedSymbols,
derived_module: SharedDerivedModule,
) -> Msg<'a> {
let find_specializations_start = SystemTime::now();
@ -4608,7 +4732,8 @@ fn build_pending_specializations<'a>(
// to know the types and abilities in our modules. Only for building *all* specializations
// do we need a global view.
abilities: AbilitiesView::Module(&abilities_store),
derived_symbols: &derived_symbols,
exposed_by_module,
derived_module: &derived_module,
};
// Add modules' decls to Procs
@ -4906,6 +5031,100 @@ fn build_pending_specializations<'a>(
}
}
/// Loads derived ability members up for specialization into the Derived module, prior to making
/// their specializations.
// TODO: right now, this runs sequentially, and no other modules are mono'd in parallel to the
// derived module.
#[allow(clippy::too_many_arguments)]
fn load_derived_partial_procs<'a>(
home: ModuleId,
arena: &'a Bump,
subs: &mut Subs,
ident_ids: &mut IdentIds,
derived_module: &SharedDerivedModule,
module_timing: &mut ModuleTiming,
target_info: TargetInfo,
exposed_by_module: &ExposedByModule,
procs_base: &mut ProcsBase<'a>,
world_abilities: &mut WorldAbilities,
) {
debug_assert_eq!(home, ModuleId::DERIVED_GEN);
let load_derived_procs_start = SystemTime::now();
let mut new_module_thunks = bumpalo::collections::Vec::new_in(arena);
let mut update_mode_ids = UpdateModeIds::new();
let derives_to_add = {
let mut derived_module = derived_module.lock().unwrap();
derived_module.iter_load_for_gen_module(subs, |symbol| {
!procs_base.partial_procs.contains_key(&symbol)
})
};
// TODO: we can be even lazier here if we move `add_def_to_module` to happen in mono. Also, the
// timings would be more accurate.
for (derived_symbol, derived_expr) in derives_to_add.into_iter() {
let mut mono_env = roc_mono::ir::Env {
arena,
subs,
home,
ident_ids,
target_info,
update_mode_ids: &mut update_mode_ids,
// call_specialization_counter=0 is reserved
call_specialization_counter: 1,
// NB: for getting pending specializations the module view is enough because we only need
// to know the types and abilities in our modules. Only for building *all* specializations
// do we need a global view.
abilities: AbilitiesView::World(world_abilities),
exposed_by_module,
derived_module,
};
let partial_proc = match derived_expr {
roc_can::expr::Expr::Closure(roc_can::expr::ClosureData {
function_type,
arguments,
loc_body,
captured_symbols,
return_type,
recursive,
..
}) => {
debug_assert!(captured_symbols.is_empty());
PartialProc::from_named_function(
&mut mono_env,
function_type,
arguments.clone(),
*loc_body,
CapturedSymbols::None,
recursive.is_recursive(),
return_type,
)
}
_ => internal_error!("Expected only functions to be derived"),
};
procs_base
.partial_procs
.insert(derived_symbol, partial_proc);
}
if !new_module_thunks.is_empty() {
new_module_thunks.extend(procs_base.module_thunks);
procs_base.module_thunks = new_module_thunks.into_bump_slice();
}
let load_derived_procs_end = SystemTime::now();
module_timing.find_specializations = load_derived_procs_end
.duration_since(load_derived_procs_start)
.unwrap();
}
fn run_task<'a>(
task: BuildTask<'a>,
arena: &'a Bump,
@ -4965,7 +5184,7 @@ fn run_task<'a>(
declarations,
dep_idents,
cached_subs,
derived_symbols,
derived_module,
} => Ok(run_solve(
module,
ident_ids,
@ -4978,7 +5197,7 @@ fn run_task<'a>(
declarations,
dep_idents,
cached_subs,
derived_symbols,
derived_module,
)),
BuildPendingSpecializations {
module_id,
@ -4990,7 +5209,8 @@ fn run_task<'a>(
imported_module_thunks,
exposed_to_host,
abilities_store,
derived_symbols,
exposed_by_module,
derived_module,
} => Ok(build_pending_specializations(
arena,
solved_subs,
@ -5002,8 +5222,9 @@ fn run_task<'a>(
layout_cache,
target_info,
exposed_to_host,
&exposed_by_module,
abilities_store,
derived_symbols,
derived_module,
)),
MakeSpecializations {
module_id,
@ -5014,7 +5235,8 @@ fn run_task<'a>(
specializations_we_must_make,
module_timing,
world_abilities,
derived_symbols,
exposed_by_module,
derived_module,
} => Ok(make_specializations(
arena,
module_id,
@ -5026,7 +5248,8 @@ fn run_task<'a>(
module_timing,
target_info,
world_abilities,
derived_symbols,
&exposed_by_module,
derived_module,
)),
}?;

View file

@ -39,16 +39,87 @@ enum Job<'a> {
}
#[derive(Default, Debug)]
struct MakeSpecializationInfo {
/// Modules to make specializations for after they are made for this module
succ: MutSet<ModuleId>,
/// Whether this module depends on specializations being made for another module
has_pred: bool,
}
#[derive(Debug)]
struct MakeSpecializationsDependents(MutMap<ModuleId, MakeSpecializationInfo>);
impl MakeSpecializationsDependents {
/// Gets the info entry for a module, or creates a default one.
fn entry(&mut self, module_id: ModuleId) -> &mut MakeSpecializationInfo {
self.0.entry(module_id).or_default()
}
fn mark_has_pred(&mut self, module_id: ModuleId) {
self.entry(module_id).has_pred = true;
}
fn add_succ(&mut self, module_id: ModuleId, succ: impl IntoIterator<Item = ModuleId>) {
// Add make specialization dependents
let entry = self.entry(module_id);
debug_assert!(
entry.succ.is_empty(),
"already added successors for this module"
);
entry.succ.extend(succ.into_iter());
// The module for derives implicitly depends on every other module
entry.succ.insert(ModuleId::DERIVED_GEN);
}
}
impl Default for MakeSpecializationsDependents {
fn default() -> Self {
let mut map: MutMap<ModuleId, MakeSpecializationInfo> = Default::default();
// The module for derives is always at the base as the last module to specialize
map.insert(
ModuleId::DERIVED_GEN,
MakeSpecializationInfo {
succ: Default::default(),
// NB: invariant - the derived module depends on every other module, and
// work can never be initiated for just the derived module!
has_pred: true,
},
);
Self(map)
}
}
#[derive(Debug)]
pub struct Dependencies<'a> {
waiting_for: MutMap<Job<'a>, MutSet<Job<'a>>>,
notifies: MutMap<Job<'a>, MutSet<Job<'a>>>,
status: MutMap<Job<'a>, Status>,
/// module -> modules to make specializations after, whether a module comes before us
make_specializations_dependents: MutMap<ModuleId, (MutSet<ModuleId>, bool)>,
make_specializations_dependents: MakeSpecializationsDependents,
}
impl<'a> Dependencies<'a> {
pub fn new(goal_phase: Phase) -> Self {
let mut deps = Self {
waiting_for: Default::default(),
notifies: Default::default(),
status: Default::default(),
make_specializations_dependents: Default::default(),
};
if goal_phase >= Phase::MakeSpecializations {
// Module for deriving is always implicitly loaded into the work graph, but it only
// comes into play for make specializations.
deps.add_to_status_for_phase(ModuleId::DERIVED_GEN, Phase::MakeSpecializations);
}
deps
}
/// Add all the dependencies for a module, return (module, phase) pairs that can make progress
pub fn add_module(
&mut self,
@ -86,24 +157,19 @@ impl<'a> Dependencies<'a> {
if goal_phase >= MakeSpecializations {
self.add_dependency(dep, module_id, Phase::MakeSpecializations);
// The module for derives implicitly depends on every other module
self.add_dependency(ModuleId::DERIVED_GEN, module_id, Phase::MakeSpecializations);
let dep_entry = self
.make_specializations_dependents
.entry(dep)
.or_insert((MutSet::default(), false));
dep_entry.1 = true;
// `dep` depends on `module_id` making specializations first
self.make_specializations_dependents.mark_has_pred(dep);
}
}
if goal_phase >= MakeSpecializations {
// Add make specialization dependents
let entry = self
.make_specializations_dependents
.entry(module_id)
.or_insert((MutSet::default(), false));
debug_assert!(entry.0.is_empty(), "already seen this dep");
entry
.0
.extend(dependencies.iter().map(|dep| *dep.as_inner()));
self.make_specializations_dependents
.add_succ(module_id, dependencies.iter().map(|dep| *dep.as_inner()));
}
// add dependencies for self
// phase i + 1 of a file always depends on phase i being completed
@ -115,20 +181,26 @@ impl<'a> Dependencies<'a> {
}
}
self.add_to_status(module_id, goal_phase);
self.add_to_status_for_all_phases(module_id, goal_phase);
output
}
fn add_to_status(&mut self, module_id: ModuleId, goal_phase: Phase) {
/// Adds a status for the given module for exactly one phase.
fn add_to_status_for_phase(&mut self, module_id: ModuleId, phase: Phase) {
if let Entry::Vacant(entry) = self.status.entry(Job::Step(module_id, phase)) {
entry.insert(Status::NotStarted);
}
}
/// Adds a status for the given module for all phases up to and including the goal phase.
fn add_to_status_for_all_phases(&mut self, module_id: ModuleId, goal_phase: Phase) {
for phase in PHASES.iter() {
if *phase > goal_phase {
break;
}
if let Entry::Vacant(entry) = self.status.entry(Job::Step(module_id, *phase)) {
entry.insert(Status::NotStarted);
}
self.add_to_status_for_phase(module_id, *phase);
}
}
@ -305,13 +377,14 @@ impl<'a> Dependencies<'a> {
pub fn reload_make_specialization_pass(&mut self) -> MutSet<(ModuleId, Phase)> {
let mut output = MutSet::default();
let mut make_specializations_dependents = Default::default();
let mut make_specializations_dependents = MakeSpecializationsDependents::default();
let default_make_specializations_dependents_len = make_specializations_dependents.0.len();
std::mem::swap(
&mut self.make_specializations_dependents,
&mut make_specializations_dependents,
);
for (&module, _) in make_specializations_dependents.iter() {
for (&module, _) in make_specializations_dependents.0.iter() {
let job = Job::Step(module, Phase::MakeSpecializations);
let status = self.status.get_mut(&job).unwrap();
debug_assert!(
@ -324,12 +397,14 @@ impl<'a> Dependencies<'a> {
// `add_dependency` borrows self as mut so we move `make_specializations_dependents` out
// for our local use. `add_dependency` should never grow the make specializations
// dependency graph.
for (&module, (succ, has_pred)) in make_specializations_dependents.iter() {
for (&module, MakeSpecializationInfo { succ, has_pred }) in
make_specializations_dependents.0.iter()
{
for &dependent in succ {
self.add_dependency(dependent, module, Phase::MakeSpecializations);
}
self.add_to_status(module, Phase::MakeSpecializations);
self.add_to_status_for_phase(module, Phase::MakeSpecializations);
if !has_pred {
output.insert((module, Phase::MakeSpecializations));
}
@ -339,9 +414,11 @@ impl<'a> Dependencies<'a> {
&mut self.make_specializations_dependents,
&mut make_specializations_dependents,
);
debug_assert!(
make_specializations_dependents.is_empty(),
"more modules were added to the graph"
debug_assert_eq!(
make_specializations_dependents.0.len(),
default_make_specializations_dependents_len,
"more modules were added to the graph: {:?}",
make_specializations_dependents
);
output

View file

@ -854,8 +854,8 @@ fn issue_2863_module_type_does_not_exist() {
Did you mean one of these?
Dict
Result
Dict
List
Box
"

View file

@ -48,22 +48,6 @@ pub enum LowLevel {
ListSwap,
ListIsUnique,
ListGetCapacity,
DictSize,
DictEmpty,
DictInsert,
DictRemove,
DictContains,
DictGetUnsafe,
DictKeys,
DictValues,
DictUnion,
DictIntersection,
DictDifference,
DictWalk,
DictGetCapacity,
SetFromList,
SetToDict,
SetGetCapacity,
NumAdd,
NumAddWrap,
NumAddChecked,
@ -130,7 +114,7 @@ pub enum LowLevel {
macro_rules! higher_order {
() => {
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith | DictWalk
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith
};
}
@ -152,7 +136,6 @@ impl LowLevel {
ListMap3 => 3,
ListMap4 => 4,
ListSortWith => 1,
DictWalk => 2,
_ => unreachable!(),
}
}
@ -211,7 +194,6 @@ macro_rules! map_symbol_to_lowlevel {
LowLevel::ListMap3 => unreachable!(),
LowLevel::ListMap4 => unreachable!(),
LowLevel::ListSortWith => unreachable!(),
LowLevel::DictWalk => unreachable!(),
// (un)boxing is handled in a custom way
LowLevel::BoxExpr => unreachable!(),
@ -223,7 +205,6 @@ macro_rules! map_symbol_to_lowlevel {
LowLevel::NumToIntChecked => unreachable!(),
LowLevel::NumToFloatChecked => unreachable!(),
LowLevel::NumDivUnchecked => unreachable!(),
LowLevel::DictEmpty => unreachable!(),
// these are used internally and not tied to a symbol
LowLevel::Hash => unimplemented!(),
@ -281,20 +262,6 @@ map_symbol_to_lowlevel! {
ListSublist <= LIST_SUBLIST_LOWLEVEL,
ListDropAt <= LIST_DROP_AT,
ListSwap <= LIST_SWAP,
DictSize <= DICT_LEN,
DictInsert <= DICT_INSERT,
DictRemove <= DICT_REMOVE,
DictContains <= DICT_CONTAINS,
DictGetUnsafe <= DICT_GET_LOWLEVEL,
DictKeys <= DICT_KEYS,
DictValues <= DICT_VALUES,
DictUnion <= DICT_UNION,
DictIntersection <= DICT_INTERSECTION,
DictDifference <= DICT_DIFFERENCE,
DictGetCapacity <= DICT_CAPACITY,
SetFromList <= SET_FROM_LIST,
SetToDict <= SET_TO_DICT,
SetGetCapacity <= SET_CAPACITY,
NumAdd <= NUM_ADD,
NumAddWrap <= NUM_ADD_WRAP,
NumAddChecked <= NUM_ADD_CHECKED_LOWLEVEL,

View file

@ -249,7 +249,7 @@ lazy_static! {
std::sync::Mutex::new(roc_collections::SmallStringInterner::with_capacity(10));
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct Interns {
pub module_ids: ModuleIds,
pub all_ident_ids: IdentIdsByModule,
@ -663,7 +663,7 @@ impl IdentIds {
}
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct IdentIdsByModule(VecMap<ModuleId, IdentIds>);
impl IdentIdsByModule {
@ -1001,10 +1001,13 @@ define_builtins! {
31 ATTR_INVALID: "#attr_invalid"
}
// Fake module for storing derived function symbols
1 DERIVED: "#Derived" => {
// Fake module for synthesizing and storing derived implementations
1 DERIVED_SYNTH: "#Derived" => {
}
2 NUM: "Num" => {
// Fake module from which derived implementations are code-generated
2 DERIVED_GEN: "#Derived_gen" => {
}
3 NUM: "Num" => {
0 NUM_NUM: "Num" // the Num.Num type alias
1 NUM_I128: "I128" // the Num.I128 type alias
2 NUM_U128: "U128" // the Num.U128 type alias
@ -1152,7 +1155,7 @@ define_builtins! {
144 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
145 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
}
3 BOOL: "Bool" => {
4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" // the Bool.Bool type alias
1 BOOL_FALSE: "False" imported // Bool.Bool = [False, True]
// NB: not strictly needed; used for finding tag names in error suggestions
@ -1165,7 +1168,7 @@ define_builtins! {
7 BOOL_EQ: "isEq"
8 BOOL_NEQ: "isNotEq"
}
4 STR: "Str" => {
5 STR: "Str" => {
0 STR_STR: "Str" imported // the Str.Str type alias
1 STR_IS_EMPTY: "isEmpty"
2 STR_APPEND: "#append" // unused
@ -1217,7 +1220,7 @@ define_builtins! {
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
49 STR_CAPACITY: "capacity"
}
5 LIST: "List" => {
6 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
1 LIST_IS_EMPTY: "isEmpty"
2 LIST_GET: "get"
@ -1288,7 +1291,7 @@ define_builtins! {
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
68 LIST_CAPACITY: "capacity"
}
6 RESULT: "Result" => {
7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" // the Result.Result type alias
1 RESULT_OK: "Ok" imported // Result.Result a e = [Ok a, Err e]
// NB: not strictly needed; used for finding tag names in error suggestions
@ -1302,8 +1305,8 @@ define_builtins! {
8 RESULT_IS_ERR: "isErr"
9 RESULT_AFTER_ERR: "afterErr"
}
7 DICT: "Dict" => {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
8 DICT: "Dict" => {
0 DICT_DICT: "Dict" // the Dict.Dict type alias
1 DICT_EMPTY: "empty"
2 DICT_SINGLE: "single"
3 DICT_GET: "get"
@ -1317,15 +1320,15 @@ define_builtins! {
10 DICT_KEYS: "keys"
11 DICT_VALUES: "values"
12 DICT_UNION: "union"
13 DICT_INTERSECTION: "intersection"
14 DICT_DIFFERENCE: "difference"
12 DICT_INSERT_ALL: "insertAll" // union
13 DICT_KEEP_SHARED: "keepShared" // intersection
14 DICT_REMOVE_ALL: "removeAll" // difference
15 DICT_GET_LOWLEVEL: "getLowlevel"
15 DICT_WITH_CAPACITY: "withCapacity"
16 DICT_CAPACITY: "capacity"
}
8 SET: "Set" => {
0 SET_SET: "Set" imported // the Set.Set type alias
9 SET: "Set" => {
0 SET_SET: "Set" // the Set.Set type alias
1 SET_EMPTY: "empty"
2 SET_SINGLE: "single"
3 SET_LEN: "len"
@ -1342,12 +1345,12 @@ define_builtins! {
14 SET_TO_DICT: "toDict"
15 SET_CAPACITY: "capacity"
}
9 BOX: "Box" => {
10 BOX: "Box" => {
0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type
1 BOX_BOX_FUNCTION: "box" // Box.box
2 BOX_UNBOX: "unbox"
}
10 ENCODE: "Encode" => {
11 ENCODE: "Encode" => {
0 ENCODE_ENCODER: "Encoder"
1 ENCODE_ENCODING: "Encoding"
2 ENCODE_TO_ENCODER: "toEncoder"
@ -1375,9 +1378,9 @@ define_builtins! {
24 ENCODE_APPEND: "append"
25 ENCODE_TO_BYTES: "toBytes"
}
11 JSON: "Json" => {
12 JSON: "Json" => {
0 JSON_JSON: "Json"
}
num_modules: 12 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)
num_modules: 13 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)
}

View file

@ -12,7 +12,7 @@ roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive" }
roc_late_solve = { path = "../late_solve" }
roc_std = { path = "../../roc_std", default-features = false }
roc_problem = { path = "../problem" }

View file

@ -602,17 +602,6 @@ impl<'a> BorrowInfState<'a> {
// always own the input list
self.own_var(*xs);
}
DictWalk { xs, state } => {
// own the default value if the function wants to own it
if !function_ps[0].borrow {
self.own_var(*state);
}
// own the data structure if the function wants to own the element
if !function_ps[1].borrow {
self.own_var(*xs);
}
}
}
// own the closure environment if the function needs to own it
@ -886,9 +875,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
match op {
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes
| StrGetCapacity | ListGetCapacity | DictGetCapacity | SetGetCapacity => {
arena.alloc_slice_copy(&[borrowed])
}
| StrGetCapacity | ListGetCapacity => arena.alloc_slice_copy(&[borrowed]),
ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
@ -940,20 +927,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
DictSize => arena.alloc_slice_copy(&[borrowed]),
DictEmpty => &[],
DictInsert => arena.alloc_slice_copy(&[owned, owned, owned]),
DictRemove => arena.alloc_slice_copy(&[owned, borrowed]),
DictContains => arena.alloc_slice_copy(&[borrowed, borrowed]),
DictGetUnsafe => arena.alloc_slice_copy(&[borrowed, borrowed]),
DictKeys | DictValues => arena.alloc_slice_copy(&[borrowed]),
DictUnion | DictDifference | DictIntersection => arena.alloc_slice_copy(&[owned, borrowed]),
// borrow function argument so we don't have to worry about RC of the closure
DictWalk => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
SetFromList => arena.alloc_slice_copy(&[owned]),
SetToDict => arena.alloc_slice_copy(&[owned]),
ListIsUnique => arena.alloc_slice_copy(&[borrowed]),

View file

@ -18,8 +18,6 @@ pub fn eq_generic<'a>(
ctx: &mut Context<'a>,
layout: Layout<'a>,
) -> Stmt<'a> {
let eq_todo = || todo!("Specialized `==` operator for `{:?}`", layout);
let main_body = match layout {
Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => {
unreachable!(
@ -30,7 +28,6 @@ pub fn eq_generic<'a>(
Layout::Builtin(Builtin::Str) => {
unreachable!("No generated helper proc for `==` on Str. Use Zig function.")
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => eq_todo(),
Layout::Builtin(Builtin::List(elem_layout)) => eq_list(root, ident_ids, ctx, elem_layout),
Layout::Struct { field_layouts, .. } => eq_struct(root, ident_ids, ctx, field_layouts),
Layout::Union(union_layout) => eq_tag_union(root, ident_ids, ctx, union_layout),

View file

@ -410,15 +410,6 @@ impl<'a> CodeGenHelp<'a> {
// needs *two* specializations for `List(RecursivePointer)`, not just one.
fn replace_rec_ptr(&self, ctx: &Context<'a>, layout: Layout<'a>) -> Layout<'a> {
match layout {
Layout::Builtin(Builtin::Dict(k, v)) => Layout::Builtin(Builtin::Dict(
self.arena.alloc(self.replace_rec_ptr(ctx, *k)),
self.arena.alloc(self.replace_rec_ptr(ctx, *v)),
)),
Layout::Builtin(Builtin::Set(k)) => Layout::Builtin(Builtin::Set(
self.arena.alloc(self.replace_rec_ptr(ctx, *k)),
)),
Layout::Builtin(Builtin::List(v)) => Layout::Builtin(Builtin::List(
self.arena.alloc(self.replace_rec_ptr(ctx, *v)),
)),
@ -543,7 +534,7 @@ fn layout_needs_helper_proc(layout: &Layout, op: HelperOp) -> bool {
// Both are fine, they were just developed at different times.
matches!(op, HelperOp::Inc | HelperOp::Dec | HelperOp::DecRef(_))
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => true,
Layout::Builtin(Builtin::List(_)) => true,
Layout::Struct { .. } => true, // note: we do generate a helper for Unit, with just a Stmt::Ret
Layout::Union(UnionLayout::NonRecursive(tags)) => !tags.is_empty(),
Layout::Union(_) => true,

View file

@ -103,7 +103,6 @@ pub fn refcount_generic<'a>(
structure: Symbol,
) -> Stmt<'a> {
debug_assert!(is_rc_implemented_yet(&layout));
let rc_todo = || todo!("Please update is_rc_implemented_yet for `{:?}`", layout);
match layout {
Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => {
@ -115,7 +114,6 @@ pub fn refcount_generic<'a>(
Layout::Builtin(Builtin::List(elem_layout)) => {
refcount_list(root, ident_ids, ctx, &layout, elem_layout, structure)
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(),
Layout::Struct { field_layouts, .. } => {
refcount_struct(root, ident_ids, ctx, field_layouts, structure)
}
@ -322,7 +320,6 @@ pub fn is_rc_implemented_yet(layout: &Layout) -> bool {
use UnionLayout::*;
match layout {
Layout::Builtin(Builtin::Dict(..) | Builtin::Set(_)) => false,
Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(elem_layout),
Layout::Builtin(_) => true,
Layout::Struct { field_layouts, .. } => field_layouts.iter().all(is_rc_implemented_yet),

View file

@ -809,22 +809,6 @@ impl<'a> Context<'a> {
let v = create_call!(function_ps.get(2));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
DictWalk { xs, state: _ } => {
let ownerships = [
// borrow data structure based on second argument of the folded function
(xs, function_ps[1]),
];
// borrow the default based on first argument of the folded function
// (state, function_ps[0])
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
let b = handle_ownerships_post!(b, ownerships);
let v = create_call!(function_ps.get(2));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
}

View file

@ -9,6 +9,7 @@ use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::abilities::SpecializationId;
use roc_can::expr::{AnnotatedMark, ClosureData, IntValue};
use roc_can::module::ExposedByModule;
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_collections::VecMap;
use roc_debug_flags::dbg_do;
@ -17,7 +18,7 @@ use roc_debug_flags::{
ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION,
ROC_PRINT_RUNTIME_ERROR_GEN,
};
use roc_derive_key::GlobalDerivedSymbols;
use roc_derive::SharedDerivedModule;
use roc_error_macros::{internal_error, todo_abilities};
use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId};
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
@ -918,7 +919,7 @@ impl<'a> SymbolSpecializations<'a> {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
pub struct ProcsBase<'a> {
pub partial_procs: BumpMap<Symbol, PartialProc<'a>>,
pub module_thunks: &'a [Symbol],
@ -1293,8 +1294,10 @@ pub struct Env<'a, 'i> {
pub target_info: TargetInfo,
pub update_mode_ids: &'i mut UpdateModeIds,
pub call_specialization_counter: u32,
// TODO: WorldAbilities and exposed_by_module share things, think about how to combine them
pub abilities: AbilitiesView<'i>,
pub derived_symbols: &'i GlobalDerivedSymbols,
pub exposed_by_module: &'i ExposedByModule,
pub derived_module: &'i SharedDerivedModule,
}
impl<'a, 'i> Env<'a, 'i> {
@ -1319,18 +1322,43 @@ impl<'a, 'i> Env<'a, 'i> {
}
pub fn is_imported_symbol(&self, symbol: Symbol) -> bool {
symbol.module_id() != self.home
let sym_module = symbol.module_id();
sym_module != self.home
// The Derived_gen module takes responsibility for code-generating symbols in the
// Derived_synth module.
&& !(self.home == ModuleId::DERIVED_GEN && sym_module == ModuleId::DERIVED_SYNTH)
}
/// While specializing the Derived_gen module, derived implementation symbols from the
/// Derived_synth module may be discovered. These implementations may not have yet been loaded
/// into the Derived_gen module, because we only load them before making specializations, and
/// not during mono itself (yet).
///
/// When this procedure returns `true`, the symbol should be marked as an external specialization,
/// so that a subsequent specializations pass loads the derived implementation into Derived_gen
/// and then code-generates appropriately.
pub fn is_unloaded_derived_symbol(&self, symbol: Symbol, procs: &Procs<'a>) -> bool {
self.home == ModuleId::DERIVED_GEN
&& symbol.module_id() == ModuleId::DERIVED_SYNTH
&& !procs.partial_procs.contains_key(symbol)
}
/// Unifies two variables and performs lambda set compaction.
/// Use this rather than [roc_unify::unify] directly!
fn unify(&mut self, left: Variable, right: Variable) -> Result<(), UnificationFailed> {
debug_assert_ne!(
self.home,
ModuleId::DERIVED_SYNTH,
"should never be monomorphizing the derived synth module!"
);
roc_late_solve::unify(
self.home,
self.arena,
self.subs,
&self.abilities,
self.derived_symbols,
self.derived_module,
self.exposed_by_module,
left,
right,
)
@ -2347,7 +2375,7 @@ fn from_can_let<'a>(
for (_specialization_mark, (var, specialized_symbol)) in
needed_specializations
{
use crate::copy::deep_copy_type_vars_into_expr;
use roc_can::copy::deep_copy_type_vars_into_expr;
let (new_def_expr_var, specialized_expr) = deep_copy_type_vars_into_expr(
env.subs,
@ -2625,6 +2653,7 @@ fn specialize_suspended<'a>(
None => {
// TODO this assumes the specialization is done by another module
// make sure this does not become a problem down the road!
debug_assert!(name.name().module_id() != name.name().module_id());
continue;
}
}
@ -5040,57 +5069,6 @@ pub fn with_hole<'a>(
}};
}
macro_rules! walk {
($oh:ident) => {{
debug_assert_eq!(arg_symbols.len(), 3);
const LIST_INDEX: usize = 0;
const DEFAULT_INDEX: usize = 1;
const CLOSURE_INDEX: usize = 2;
let xs = arg_symbols[LIST_INDEX];
let state = arg_symbols[DEFAULT_INDEX];
let stmt = match_on_closure_argument!($oh, [xs, state]);
// because of a hack to implement List.product and List.sum, we need to also
// assign to symbols here. Normally the arguments to a lowlevel function are
// all symbols anyway, but because of this hack the closure symbol can be an
// actual closure, and the default is either the number 1 or 0
// this can be removed when we define builtin modules as proper modules
let stmt = assign_to_symbol(
env,
procs,
layout_cache,
args[LIST_INDEX].0,
Loc::at_zero(args[LIST_INDEX].1.clone()),
arg_symbols[LIST_INDEX],
stmt,
);
let stmt = assign_to_symbol(
env,
procs,
layout_cache,
args[DEFAULT_INDEX].0,
Loc::at_zero(args[DEFAULT_INDEX].1.clone()),
arg_symbols[DEFAULT_INDEX],
stmt,
);
assign_to_symbol(
env,
procs,
layout_cache,
args[CLOSURE_INDEX].0,
Loc::at_zero(args[CLOSURE_INDEX].1.clone()),
arg_symbols[CLOSURE_INDEX],
stmt,
)
}};
}
use LowLevel::*;
match op {
ListMap => {
@ -5103,7 +5081,6 @@ pub fn with_hole<'a>(
let xs = arg_symbols[0];
match_on_closure_argument!(ListSortWith, [xs])
}
DictWalk => walk!(DictWalk),
ListMap2 => {
debug_assert_eq!(arg_symbols.len(), 3);
@ -7734,7 +7711,9 @@ fn call_by_name_help<'a>(
assigned,
hole,
)
} else if env.is_imported_symbol(proc_name.name()) {
} else if env.is_imported_symbol(proc_name.name())
|| env.is_unloaded_derived_symbol(proc_name.name(), procs)
{
add_needed_external(procs, env, original_fn_var, proc_name);
debug_assert_ne!(proc_name.name().module_id(), ModuleId::ATTR);

View file

@ -22,17 +22,17 @@ use ven_pretty::{DocAllocator, DocBuilder};
// if your changes cause this number to go down, great!
// please change it to the lower number.
// if it went up, maybe check that the change is really required
roc_error_macros::assert_sizeof_aarch64!(Builtin, 3 * 8);
roc_error_macros::assert_sizeof_aarch64!(Builtin, 2 * 8);
roc_error_macros::assert_sizeof_aarch64!(Layout, 4 * 8);
roc_error_macros::assert_sizeof_aarch64!(UnionLayout, 3 * 8);
roc_error_macros::assert_sizeof_aarch64!(LambdaSet, 3 * 8);
roc_error_macros::assert_sizeof_wasm!(Builtin, 3 * 4);
roc_error_macros::assert_sizeof_wasm!(Builtin, 2 * 4);
roc_error_macros::assert_sizeof_wasm!(Layout, 6 * 4);
roc_error_macros::assert_sizeof_wasm!(UnionLayout, 3 * 4);
roc_error_macros::assert_sizeof_wasm!(LambdaSet, 3 * 4);
roc_error_macros::assert_sizeof_default!(Builtin, 3 * 8);
roc_error_macros::assert_sizeof_default!(Builtin, 2 * 8);
roc_error_macros::assert_sizeof_default!(Layout, 4 * 8);
roc_error_macros::assert_sizeof_default!(UnionLayout, 3 * 8);
roc_error_macros::assert_sizeof_default!(LambdaSet, 3 * 8);
@ -1155,8 +1155,6 @@ pub enum Builtin<'a> {
Bool,
Decimal,
Str,
Dict(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>),
List(&'a Layout<'a>),
}
@ -1809,8 +1807,6 @@ impl<'a> Builtin<'a> {
/// Number of machine words in an empty one of these
pub const STR_WORDS: u32 = 3;
pub const DICT_WORDS: u32 = 3;
pub const SET_WORDS: u32 = Builtin::DICT_WORDS; // Set is an alias for Dict with {} for value
pub const LIST_WORDS: u32 = 3;
/// Layout of collection wrapper for List, Str, Dict, and Set - a struct of (pointer, length, capacity).
@ -1829,8 +1825,6 @@ impl<'a> Builtin<'a> {
Bool => Builtin::I1_SIZE,
Decimal => Builtin::DECIMAL_SIZE,
Str => Builtin::STR_WORDS * ptr_width,
Dict(_, _) => Builtin::DICT_WORDS * ptr_width,
Set(_) => Builtin::SET_WORDS * ptr_width,
List(_) => Builtin::LIST_WORDS * ptr_width,
}
}
@ -1849,8 +1843,6 @@ impl<'a> Builtin<'a> {
Float(float_width) => float_width.alignment_bytes(target_info),
Bool => align_of::<bool>() as u32,
Decimal => IntWidth::I128.alignment_bytes(target_info),
Dict(_, _) => ptr_width,
Set(_) => ptr_width,
// we often treat these as i128 (64-bit systems)
// or i64 (32-bit systems).
//
@ -1867,7 +1859,7 @@ impl<'a> Builtin<'a> {
match self {
Int(_) | Float(_) | Bool | Decimal => true,
Str | Dict(_, _) | Set(_) | List(_) => false,
Str | List(_) => false,
}
}
@ -1878,8 +1870,7 @@ impl<'a> Builtin<'a> {
match self {
Int(_) | Float(_) | Bool | Decimal => false,
List(_) => true,
Str | Dict(_, _) | Set(_) => true,
Str => true,
}
}
@ -1926,14 +1917,6 @@ impl<'a> Builtin<'a> {
List(layout) => alloc
.text("List ")
.append(layout.to_doc(alloc, Parens::InTypeParam)),
Set(layout) => alloc
.text("Set ")
.append(layout.to_doc(alloc, Parens::InTypeParam)),
Dict(key_layout, value_layout) => alloc
.text("Dict ")
.append(key_layout.to_doc(alloc, Parens::InTypeParam))
.append(" ")
.append(value_layout.to_doc(alloc, Parens::InTypeParam)),
}
}
@ -1942,11 +1925,6 @@ impl<'a> Builtin<'a> {
let allocation = match self {
Builtin::Str => ptr_width,
Builtin::Dict(k, v) => k
.alignment_bytes(target_info)
.max(v.alignment_bytes(target_info))
.max(ptr_width),
Builtin::Set(k) => k.alignment_bytes(target_info).max(ptr_width),
Builtin::List(e) => e.alignment_bytes(target_info).max(ptr_width),
// The following are usually not heap-allocated, but they might be when inside a Box.
Builtin::Int(int_width) => int_width.alignment_bytes(target_info).max(ptr_width),
@ -2079,8 +2057,6 @@ fn layout_from_flat_type<'a>(
Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)),
Symbol::LIST_LIST => list_layout_from_elem(env, args[0]),
Symbol::DICT_DICT => dict_layout_from_key_value(env, args[0], args[1]),
Symbol::SET_SET => dict_layout_from_key_value(env, args[0], Variable::EMPTY_RECORD),
Symbol::BOX_BOX_TYPE => {
// Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer
debug_assert_eq!(args.len(), 1);
@ -3124,39 +3100,6 @@ fn layout_from_num_content<'a>(
}
}
fn dict_layout_from_key_value<'a>(
env: &mut Env<'a, '_>,
key_var: Variable,
value_var: Variable,
) -> Result<Layout<'a>, LayoutProblem> {
let is_variable = |content| matches!(content, &Content::FlexVar(_) | &Content::RigidVar(_));
let key_content = env.subs.get_content_without_compacting(key_var);
let value_content = env.subs.get_content_without_compacting(value_var);
let key_layout = if is_variable(key_content) {
Layout::VOID
} else {
// NOTE: cannot re-use Content, because it may be recursive
// then some state is not correctly kept, we have to go through from_var
Layout::from_var(env, key_var)?
};
let value_layout = if is_variable(value_content) {
Layout::VOID
} else {
// NOTE: cannot re-use Content, because it may be recursive
// then some state is not correctly kept, we have to go through from_var
Layout::from_var(env, value_var)?
};
// This is a normal list.
Ok(Layout::Builtin(Builtin::Dict(
env.arena.alloc(key_layout),
env.arena.alloc(value_layout),
)))
}
pub fn list_layout_from_elem<'a>(
env: &mut Env<'a, '_>,
element_var: Variable,

View file

@ -4,7 +4,6 @@
pub mod borrow;
pub mod code_gen_help;
mod copy;
pub mod inc_dec;
pub mod ir;
pub mod layout;

View file

@ -23,10 +23,6 @@ pub enum HigherOrder {
ListSortWith {
xs: Symbol,
},
DictWalk {
xs: Symbol,
state: Symbol,
},
}
impl HigherOrder {
@ -37,7 +33,6 @@ impl HigherOrder {
HigherOrder::ListMap3 { .. } => 3,
HigherOrder::ListMap4 { .. } => 4,
HigherOrder::ListSortWith { .. } => 2,
HigherOrder::DictWalk { .. } => 2,
}
}
@ -51,7 +46,6 @@ impl HigherOrder {
ListMap2 { .. } => 3,
ListMap3 { .. } => 4,
ListMap4 { .. } => 5,
DictWalk { .. } => 3,
}
}
@ -85,18 +79,6 @@ enum FirstOrder {
ListAppend,
ListPrepend,
ListSwap,
DictSize,
DictEmpty,
DictInsert,
DictRemove,
DictContains,
DictGetUnsafe,
DictKeys,
DictValues,
DictUnion,
DictIntersection,
DictDifference,
SetFromList,
NumAdd,
NumAddWrap,
NumAddChecked,

View file

@ -298,7 +298,7 @@ pub enum TypeDef<'a> {
Opaque {
header: TypeHeader<'a>,
typ: Loc<TypeAnnotation<'a>>,
derived: Option<Loc<Derived<'a>>>,
derived: Option<Loc<HasAbilities<'a>>>,
},
/// An ability definition. E.g.
@ -435,17 +435,41 @@ pub struct HasClause<'a> {
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Derived<'a> {
/// `has [Eq, Hash]`
Has(Collection<'a, AbilityName<'a>>),
pub enum HasImpls<'a> {
// `{ eq: myEq }`
HasImpls(Collection<'a, Loc<AssignedField<'a, TypeAnnotation<'a>>>>),
// We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a Derived<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a Derived<'a>, &'a [CommentOrNewline<'a>]),
SpaceBefore(&'a HasImpls<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a HasImpls<'a>, &'a [CommentOrNewline<'a>]),
}
impl Derived<'_> {
pub fn collection(&self) -> &Collection<AbilityName> {
/// `Eq` or `Eq { eq: myEq }`
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum HasAbility<'a> {
HasAbility {
/// Should be a zero-argument `Apply` or an error; we'll check this in canonicalization
ability: Loc<TypeAnnotation<'a>>,
impls: Option<Loc<HasImpls<'a>>>,
},
// We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a HasAbility<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a HasAbility<'a>, &'a [CommentOrNewline<'a>]),
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum HasAbilities<'a> {
/// `has [Eq { eq: myEq }, Hash]`
Has(Collection<'a, Loc<HasAbility<'a>>>),
// We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a HasAbilities<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a HasAbilities<'a>, &'a [CommentOrNewline<'a>]),
}
impl HasAbilities<'_> {
pub fn collection(&self) -> &Collection<Loc<HasAbility>> {
let mut it = self;
loop {
match it {
@ -879,6 +903,12 @@ impl<'a, T: Debug> Debug for Collection<'a, T> {
}
}
impl<'a, T> Default for Collection<'a, T> {
fn default() -> Self {
Self::empty()
}
}
pub trait Spaceable<'a> {
fn before(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self;
fn after(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self;
@ -967,12 +997,30 @@ impl<'a> Spaceable<'a> for Has<'a> {
}
}
impl<'a> Spaceable<'a> for Derived<'a> {
impl<'a> Spaceable<'a> for HasImpls<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Derived::SpaceBefore(self, spaces)
HasImpls::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Derived::SpaceAfter(self, spaces)
HasImpls::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for HasAbility<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
HasAbility::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
HasAbility::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for HasAbilities<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
HasAbilities::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
HasAbilities::SpaceAfter(self, spaces)
}
}
@ -1059,6 +1107,8 @@ impl_extract_spaces!(Pattern);
impl_extract_spaces!(Tag);
impl_extract_spaces!(AssignedField<T>);
impl_extract_spaces!(TypeAnnotation);
impl_extract_spaces!(HasAbility);
impl_extract_spaces!(HasImpls);
impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> {
type Item = T;

View file

@ -1,6 +1,6 @@
use crate::ast::{
AssignedField, Collection, CommentOrNewline, Defs, Derived, Expr, ExtractSpaces, Has, Pattern,
Spaceable, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Has, HasAbilities,
Pattern, Spaceable, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use crate::blankspace::{
space0_after_e, space0_around_ee, space0_before_e, space0_before_optional_after, space0_e,
@ -953,7 +953,7 @@ fn alias_signature_with_space_before<'a>(
fn opaque_signature_with_space_before<'a>(
min_indent: u32,
) -> impl Parser<'a, (Loc<TypeAnnotation<'a>>, Option<Loc<Derived<'a>>>), EExpr<'a>> {
) -> impl Parser<'a, (Loc<TypeAnnotation<'a>>, Option<Loc<HasAbilities<'a>>>), EExpr<'a>> {
and!(
specialize(
EExpr::Type,
@ -966,7 +966,7 @@ fn opaque_signature_with_space_before<'a>(
optional(specialize(
EExpr::Type,
space0_before_e(
type_annotation::has_derived(min_indent),
type_annotation::has_abilities(min_indent),
min_indent,
EType::TIndentStart,
),

View file

@ -103,6 +103,7 @@ impl_space_problem! {
ETypeRecord<'a>,
ETypeTagUnion<'a>,
ETypedIdent<'a>,
ETypeAbilityImpl<'a>,
EWhen<'a>,
EAbility<'a>,
PInParens<'a>,
@ -578,6 +579,7 @@ pub enum EType<'a> {
TFunctionArgument(Position),
TWhereBar(Position),
THasClause(Position),
TAbilityImpl(ETypeAbilityImpl<'a>, Position),
///
TIndentStart(Position),
TIndentEnd(Position),
@ -648,6 +650,42 @@ pub enum ETypeInlineAlias {
ArgumentNotLowercase(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ETypeAbilityImpl<'a> {
End(Position),
Open(Position),
Field(Position),
Colon(Position),
Optional(Position),
Type(&'a EType<'a>, Position),
Space(BadInputError, Position),
IndentOpen(Position),
IndentColon(Position),
IndentOptional(Position),
IndentEnd(Position),
}
impl<'a> From<ETypeRecord<'a>> for ETypeAbilityImpl<'a> {
fn from(e: ETypeRecord<'a>) -> Self {
match e {
ETypeRecord::End(p) => ETypeAbilityImpl::End(p),
ETypeRecord::Open(p) => ETypeAbilityImpl::Open(p),
ETypeRecord::Field(p) => ETypeAbilityImpl::Field(p),
ETypeRecord::Colon(p) => ETypeAbilityImpl::Colon(p),
ETypeRecord::Optional(p) => ETypeAbilityImpl::Optional(p),
ETypeRecord::Type(t, p) => ETypeAbilityImpl::Type(t, p),
ETypeRecord::Space(s, p) => ETypeAbilityImpl::Space(s, p),
ETypeRecord::IndentOpen(p) => ETypeAbilityImpl::IndentOpen(p),
ETypeRecord::IndentColon(p) => ETypeAbilityImpl::IndentColon(p),
ETypeRecord::IndentOptional(p) => ETypeAbilityImpl::IndentOptional(p),
ETypeRecord::IndentEnd(p) => ETypeAbilityImpl::IndentEnd(p),
}
}
}
#[derive(Debug)]
pub struct SourceError<'a, T> {
pub problem: T,

View file

@ -1,16 +1,16 @@
use crate::ast::{
AssignedField, CommentOrNewline, Derived, HasClause, Pattern, Spaced, Tag, TypeAnnotation,
TypeHeader,
AssignedField, CommentOrNewline, HasAbilities, HasAbility, HasClause, HasImpls, Pattern,
Spaced, Tag, TypeAnnotation, TypeHeader,
};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::lowercase_ident;
use crate::keyword;
use crate::parser::then;
use crate::parser::{
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, word3, EType,
ETypeApply, ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
Progress::{self, *},
};
use crate::parser::{then, ETypeAbilityImpl};
use crate::state::State;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
@ -478,8 +478,8 @@ fn has_clause_chain<'a>(
}
}
/// Parse a has-derived clause, e.g. `has [Eq, Hash]`.
pub fn has_derived<'a>(min_indent: u32) -> impl Parser<'a, Loc<Derived<'a>>, EType<'a>> {
/// Parse a has-abilities clause, e.g. `has [Eq, Hash]`.
pub fn has_abilities<'a>(min_indent: u32) -> impl Parser<'a, Loc<HasAbilities<'a>>, EType<'a>> {
skip_first!(
// Parse "has"; we don't care about this keyword
word3(b'h', b'a', b's', EType::THasClause),
@ -488,19 +488,51 @@ pub fn has_derived<'a>(min_indent: u32) -> impl Parser<'a, Loc<Derived<'a>>, ETy
loc!(map!(
collection_trailing_sep_e!(
word1(b'[', EType::TStart),
specialize(EType::TApply, loc!(parse_concrete_type)),
loc!(parse_has_ability(min_indent)),
word1(b',', EType::TEnd),
word1(b']', EType::TEnd),
min_indent + 1,
EType::TStart,
EType::TIndentEnd,
TypeAnnotation::SpaceBefore
HasAbility::SpaceBefore
),
Derived::Has
HasAbilities::Has
)),
min_indent + 1,
EType::TIndentEnd,
)
)
}
fn parse_has_ability<'a>(min_indent: u32) -> impl Parser<'a, HasAbility<'a>, EType<'a>> {
map!(
and!(
loc!(specialize(EType::TApply, parse_concrete_type)),
optional(space0_before_e(
loc!(map!(
specialize(
EType::TAbilityImpl,
collection_trailing_sep_e!(
word1(b'{', ETypeAbilityImpl::Open),
specialize(
|e: ETypeRecord<'_>, _| e.into(),
loc!(record_type_field(min_indent + 1))
),
word1(b',', ETypeAbilityImpl::End),
word1(b'}', ETypeAbilityImpl::End),
min_indent,
ETypeAbilityImpl::Open,
ETypeAbilityImpl::IndentEnd,
AssignedField::SpaceBefore
)
),
HasImpls::HasImpls
)),
min_indent + 1,
EType::TIndentEnd
)
))
),
|(ability, impls): (_, Option<_>)| { HasAbility::HasAbility { ability, impls } }
)
}

View file

@ -4,27 +4,69 @@ Defs(
Index(0),
Index(1),
Index(2),
Index(3),
Index(4),
Index(5),
Index(6),
Index(7),
Index(8),
Index(9),
],
regions: [
@0-7,
@24-44,
@61-81,
@103-110,
@139-146,
@167-174,
@201-208,
@235-242,
@251-271,
@305-312,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 2),
Slice(start = 2, length = 2),
Slice(start = 4, length = 2),
Slice(start = 6, length = 2),
Slice(start = 8, length = 2),
Slice(start = 10, length = 2),
Slice(start = 12, length = 2),
Slice(start = 14, length = 2),
Slice(start = 16, length = 2),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 2, length = 0),
Slice(start = 4, length = 0),
Slice(start = 6, length = 0),
Slice(start = 8, length = 0),
Slice(start = 10, length = 0),
Slice(start = 12, length = 0),
Slice(start = 14, length = 0),
Slice(start = 16, length = 0),
Slice(start = 18, length = 0),
],
spaces: [
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
],
type_defs: [
Opaque {
@ -40,16 +82,22 @@ Defs(
derived: Some(
@12-22 Has(
[
@13-15 Apply(
@13-15 HasAbility {
ability: @13-15 Apply(
"",
"Eq",
[],
),
@17-21 Apply(
impls: None,
},
@17-21 HasAbility {
ability: @17-21 Apply(
"",
"Hash",
[],
),
impls: None,
},
],
),
),
@ -77,16 +125,22 @@ Defs(
derived: Some(
@49-59 Has(
[
@50-52 Apply(
@50-52 HasAbility {
ability: @50-52 Apply(
"",
"Eq",
[],
),
@54-58 Apply(
impls: None,
},
@54-58 HasAbility {
ability: @54-58 Apply(
"",
"Hash",
[],
),
impls: None,
},
],
),
),
@ -115,16 +169,22 @@ Defs(
@91-101 SpaceBefore(
Has(
[
@92-94 Apply(
@92-94 HasAbility {
ability: @92-94 Apply(
"",
"Eq",
[],
),
@96-100 Apply(
impls: None,
},
@96-100 HasAbility {
ability: @96-100 Apply(
"",
"Hash",
[],
),
impls: None,
},
],
),
[
@ -133,10 +193,292 @@ Defs(
),
),
},
Opaque {
header: TypeHeader {
name: @103-104 "A",
vars: [],
},
typ: @108-110 Apply(
"",
"U8",
[],
),
derived: Some(
@115-137 Has(
[
@116-123 HasAbility {
ability: @116-118 Apply(
"",
"Eq",
[],
),
impls: Some(
@119-123 HasImpls(
[
@120-122 LabelOnly(
@120-122 "eq",
),
],
),
),
},
@125-136 HasAbility {
ability: @125-129 Apply(
"",
"Hash",
[],
),
impls: Some(
@130-136 HasImpls(
[
@131-135 LabelOnly(
@131-135 "hash",
),
],
),
),
},
],
),
),
},
Opaque {
header: TypeHeader {
name: @139-140 "A",
vars: [],
},
typ: @144-146 Apply(
"",
"U8",
[],
),
derived: Some(
@151-165 Has(
[
@152-164 HasAbility {
ability: @152-154 Apply(
"",
"Eq",
[],
),
impls: Some(
@155-164 HasImpls(
[
@156-158 LabelOnly(
@156-158 "eq",
),
@160-163 LabelOnly(
@160-163 "eq1",
),
],
),
),
},
],
),
),
},
Opaque {
header: TypeHeader {
name: @167-168 "A",
vars: [],
},
typ: @172-174 Apply(
"",
"U8",
[],
),
derived: Some(
@179-199 Has(
[
@180-192 HasAbility {
ability: @180-182 Apply(
"",
"Eq",
[],
),
impls: Some(
@183-192 HasImpls(
[
@184-186 LabelOnly(
@184-186 "eq",
),
@188-191 LabelOnly(
@188-191 "eq1",
),
],
),
),
},
@194-198 HasAbility {
ability: @194-198 Apply(
"",
"Hash",
[],
),
impls: None,
},
],
),
),
},
Opaque {
header: TypeHeader {
name: @201-202 "A",
vars: [],
},
typ: @206-208 Apply(
"",
"U8",
[],
),
derived: Some(
@213-233 Has(
[
@214-218 HasAbility {
ability: @214-218 Apply(
"",
"Hash",
[],
),
impls: None,
},
@220-232 HasAbility {
ability: @220-222 Apply(
"",
"Eq",
[],
),
impls: Some(
@223-232 HasImpls(
[
@224-226 LabelOnly(
@224-226 "eq",
),
@228-231 LabelOnly(
@228-231 "eq1",
),
],
),
),
},
],
),
),
},
Opaque {
header: TypeHeader {
name: @235-236 "A",
vars: [],
},
typ: @240-242 Apply(
"",
"U8",
[],
),
derived: Some(
@247-249 Has(
[],
),
),
},
Opaque {
header: TypeHeader {
name: @251-252 "A",
vars: [],
},
typ: @256-271 Where(
@256-257 BoundVariable(
"a",
),
[
@260-271 HasClause {
var: @260-261 "a",
ability: @266-271 Apply(
"",
"Other",
[],
),
},
],
),
derived: Some(
@281-303 SpaceBefore(
Has(
[
@282-289 HasAbility {
ability: @282-284 Apply(
"",
"Eq",
[],
),
impls: Some(
@285-289 HasImpls(
[
@286-288 LabelOnly(
@286-288 "eq",
),
],
),
),
},
@291-302 HasAbility {
ability: @291-295 Apply(
"",
"Hash",
[],
),
impls: Some(
@296-302 HasImpls(
[
@297-301 LabelOnly(
@297-301 "hash",
),
],
),
),
},
],
),
[
Newline,
],
),
),
},
Opaque {
header: TypeHeader {
name: @305-306 "A",
vars: [],
},
typ: @310-312 Apply(
"",
"U8",
[],
),
derived: Some(
@317-324 Has(
[
@318-323 HasAbility {
ability: @318-320 Apply(
"",
"Eq",
[],
),
impls: Some(
@321-323 HasImpls(
[],
),
),
},
],
),
),
},
],
value_defs: [],
},
@103-104 SpaceBefore(
@326-327 SpaceBefore(
Num(
"0",
),

View file

@ -5,4 +5,19 @@ A := a | a has Other has [Eq, Hash]
A := a | a has Other
has [Eq, Hash]
A := U8 has [Eq {eq}, Hash {hash}]
A := U8 has [Eq {eq, eq1}]
A := U8 has [Eq {eq, eq1}, Hash]
A := U8 has [Hash, Eq {eq, eq1}]
A := U8 has []
A := a | a has Other
has [Eq {eq}, Hash {hash}]
A := U8 has [Eq {}]
0

View file

@ -14,6 +14,7 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive" }
roc_problem = { path = "../problem" }
roc_unify = { path = "../unify" }
roc_debug_flags = { path = "../debug_flags" }
@ -28,7 +29,7 @@ roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" }
roc_derive_key = { path = "../derive_key", features = ["debug-derived-symbols"] }
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
pretty_assertions = "1.0.0"
indoc = "1.0.3"
tempfile = "3.2.0"

View file

@ -55,7 +55,7 @@ pub enum Unfulfilled {
},
}
/// Indexes a deriving of an ability for an opaque type.
/// Indexes a requested deriving of an ability for an opaque type.
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct RequestedDeriveKey {
pub opaque: Symbol,
@ -553,6 +553,34 @@ impl ObligationCache<'_> {
}
Erroneous(_) => return Err(var),
},
#[rustfmt::skip]
Alias(
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8
| Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16
| Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32
| Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64
| Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128
| Symbol::NUM_I8 | Symbol::NUM_SIGNED8
| Symbol::NUM_I16 | Symbol::NUM_SIGNED16
| Symbol::NUM_I32 | Symbol::NUM_SIGNED32
| Symbol::NUM_I64 | Symbol::NUM_SIGNED64
| Symbol::NUM_I128 | Symbol::NUM_SIGNED128
| Symbol::NUM_NAT | Symbol::NUM_NATURAL
| Symbol::NUM_F32 | Symbol::NUM_BINARY32
| Symbol::NUM_F64 | Symbol::NUM_BINARY64
| Symbol::NUM_DEC | Symbol::NUM_DECIMAL,
_,
_,
_,
) => {
// yes
}
Alias(
Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT,
_,
real_var,
_,
) => stack.push(*real_var),
Alias(name, _, _, AliasKind::Opaque) => {
let opaque = *name;
if self
@ -562,27 +590,6 @@ impl ObligationCache<'_> {
return Err(var);
}
}
Alias(
Symbol::NUM_U8
| Symbol::NUM_U16
| Symbol::NUM_U32
| Symbol::NUM_U64
| Symbol::NUM_U128
| Symbol::NUM_I8
| Symbol::NUM_I16
| Symbol::NUM_I32
| Symbol::NUM_I64
| Symbol::NUM_I128
| Symbol::NUM_NAT
| Symbol::NUM_F32
| Symbol::NUM_F64
| Symbol::NUM_DEC,
_,
_,
_,
) => {
// yes
}
Alias(_, arguments, real_type_var, _) => {
push_var_slice!(arguments.all_variables());
stack.push(*real_type_var);

View file

@ -2,12 +2,12 @@ use crate::solve::{self, Aliases};
use roc_can::abilities::{AbilitiesStore, ResolvedSpecializations};
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
use roc_can::expr::PendingDerives;
use roc_can::module::RigidVariables;
use roc_can::module::{ExposedByModule, RigidVariables};
use roc_collections::all::MutMap;
use roc_collections::VecMap;
use roc_derive_key::GlobalDerivedSymbols;
use roc_derive::SharedDerivedModule;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_module::symbol::{ModuleId, Symbol};
use roc_types::subs::{Content, ExposedTypesStorageSubs, FlatType, StorageSubs, Subs, Variable};
use roc_types::types::Alias;
@ -54,6 +54,7 @@ pub struct SolvedModule {
#[allow(clippy::too_many_arguments)] // TODO: put params in a context/env var
pub fn run_solve(
home: ModuleId,
constraints: &Constraints,
constraint: ConstraintSoa,
rigid_variables: RigidVariables,
@ -61,7 +62,8 @@ pub fn run_solve(
mut aliases: Aliases,
mut abilities_store: AbilitiesStore,
pending_derives: PendingDerives,
derived_symbols: GlobalDerivedSymbols,
exposed_by_module: &ExposedByModule,
derived_module: SharedDerivedModule,
) -> (
Solved<Subs>,
solve::Env,
@ -86,6 +88,7 @@ pub fn run_solve(
// Run the solver to populate Subs.
let (solved_subs, solved_env) = solve::run(
home,
constraints,
&mut problems,
subs,
@ -93,7 +96,8 @@ pub fn run_solve(
&constraint,
pending_derives,
&mut abilities_store,
derived_symbols,
exposed_by_module,
derived_module,
);
(solved_subs, solved_env, problems, abilities_store)
@ -101,27 +105,28 @@ pub fn run_solve(
/// Copies exposed types and all ability specializations, which may be implicitly exposed.
pub fn exposed_types_storage_subs(
home: ModuleId,
solved_subs: &mut Solved<Subs>,
exposed_vars_by_symbol: &[(Symbol, Variable)],
solved_specializations: &ResolvedSpecializations,
abilities_store: &AbilitiesStore,
) -> ExposedTypesStorageSubs {
let subs = solved_subs.inner_mut();
let mut storage_subs = StorageSubs::new(Subs::new());
let mut stored_vars_by_symbol = VecMap::with_capacity(exposed_vars_by_symbol.len());
let mut stored_specialization_lambda_set_vars =
VecMap::with_capacity(solved_specializations.len());
for (symbol, var) in exposed_vars_by_symbol.iter() {
let new_var = storage_subs.import_variable_from(subs, *var).variable;
stored_vars_by_symbol.insert(*symbol, new_var);
}
let mut stored_specialization_lambda_set_vars =
VecMap::with_capacity(solved_specializations.len());
for (_, member_specialization) in solved_specializations.iter() {
for (_, &specialization_lset_var) in member_specialization.specialization_lambda_sets.iter()
{
let specialization_lset_ambient_function_var = subs
.get_lambda_set(specialization_lset_var)
.ambient_function;
for (_, &lset_var) in member_specialization.specialization_lambda_sets.iter() {
let specialization_lset_ambient_function_var =
subs.get_lambda_set(lset_var).ambient_function;
// Import the ambient function of this specialization lambda set; that will import the
// lambda set as well. The ambient function is needed for the lambda set compaction
@ -140,14 +145,29 @@ pub fn exposed_types_storage_subs(
roc_types::subs::SubsFmtContent(content, storage_subs.as_inner())
),
};
stored_specialization_lambda_set_vars
.insert(specialization_lset_var, imported_lset_var);
stored_specialization_lambda_set_vars.insert(lset_var, imported_lset_var);
}
}
// Store the regioned lambda sets of the ability members defined in this module.
let stored_ability_member_vars = abilities_store
.root_ability_members()
.iter()
.filter_map(|(member, data)| {
if member.module_id() == home {
let var = data.signature_var();
let imported_var = storage_subs.import_variable_from(subs, var).variable;
Some((var, imported_var))
} else {
None
}
})
.collect();
ExposedTypesStorageSubs {
storage_subs,
stored_vars_by_symbol,
stored_specialization_lambda_set_vars,
stored_ability_member_vars,
}
}

View file

@ -9,20 +9,22 @@ use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::{Constraints, Cycle, LetConstraint, OpportunisticResolve};
use roc_can::expected::{Expected, PExpected};
use roc_can::expr::PendingDerives;
use roc_can::module::ExposedByModule;
use roc_collections::all::MutMap;
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::{ROC_TRACE_COMPACTION, ROC_VERIFY_RIGID_LET_GENERALIZED};
use roc_derive_key::{DeriveError, Derived, GlobalDerivedSymbols};
use roc_derive::SharedDerivedModule;
use roc_derive_key::{DeriveError, DeriveKey};
use roc_error_macros::internal_error;
use roc_module::ident::TagName;
use roc_module::symbol::{ModuleId, Symbol};
use roc_problem::can::CycleEntry;
use roc_region::all::{Loc, Region};
use roc_types::subs::{
self, AliasVariables, Content, Descriptor, FlatType, GetSubsSlice, LambdaSet, Mark,
OptVariable, Rank, RecordFields, Subs, SubsIndex, SubsSlice, UlsOfVar, UnionLabels,
UnionLambdas, UnionTags, Variable, VariableSubsSlice,
self, get_member_lambda_sets_at_region, AliasVariables, Content, Descriptor, FlatType,
GetSubsSlice, LambdaSet, Mark, OptVariable, Rank, RecordFields, Subs, SubsIndex, SubsSlice,
UlsOfVar, UnionLabels, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::Type::{self, *};
use roc_types::types::{
@ -314,7 +316,8 @@ impl Aliases {
let (typ, delayed_variables, &mut kind) =
match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) {
None => internal_error!(
"Alias not registered in delayed aliases! {:?}",
"Alias {:?} not registered in delayed aliases! {:?}",
symbol,
&self.aliases
),
Some((_, typ, delayed_variables, kind)) => (typ, delayed_variables, kind),
@ -322,6 +325,42 @@ impl Aliases {
let mut substitutions: MutMap<_, _> = Default::default();
let old_type_variables = delayed_variables.type_variables(&mut self.variables);
let new_type_variables = &subs.variables[alias_variables.type_variables().indices()];
let some_new_vars_are_equivalent = {
// In practice the number of type variables is tiny, so just do a quadratic check
// without allocating.
let mut some_equivalent = false;
for (i, var) in new_type_variables.iter().enumerate() {
for other_var in new_type_variables.iter().skip(i + 1) {
some_equivalent = some_equivalent || var == other_var;
}
}
some_equivalent
};
// If some type variables are equivalent, we have to work over a cloned type variable,
// otherwise we will leave in place an alias without preserving the property of unique
// type variables.
//
// For example, if a delayed alias `Foo a b` is instantiated with args `t1 t1` without cloning,
// then the delayed alias would be updated to `Foo t1 t1`, and now the distinction between the
// two type variables is lost.
let can_reuse_old_definition = !some_new_vars_are_equivalent;
for (old, new) in old_type_variables.iter_mut().zip(new_type_variables) {
// if constraint gen duplicated a type these variables could be the same
// (happens very often in practice)
if old.var != *new {
substitutions.insert(old.var, *new);
if can_reuse_old_definition {
old.var = *new;
}
}
}
for OptAbleVar {
var: rec_var,
opt_ability,
@ -332,20 +371,10 @@ impl Aliases {
debug_assert!(opt_ability.is_none());
let new_var = subs.fresh_unnamed_flex_var();
substitutions.insert(*rec_var, new_var);
if can_reuse_old_definition {
*rec_var = new_var;
}
let old_type_variables = delayed_variables.type_variables(&mut self.variables);
let new_type_variables = &subs.variables[alias_variables.type_variables().indices()];
for (old, new) in old_type_variables.iter_mut().zip(new_type_variables) {
// if constraint gen duplicated a type these variables could be the same
// (happens very often in practice)
if old.var != *new {
substitutions.insert(old.var, *new);
old.var = *new;
}
}
let old_lambda_set_variables = delayed_variables.lambda_set_variables(&mut self.variables);
@ -359,10 +388,19 @@ impl Aliases {
debug_assert!(old.opt_ability.is_none());
if old.var != *new {
substitutions.insert(old.var, *new);
if can_reuse_old_definition {
old.var = *new;
}
}
}
if !can_reuse_old_definition {
let mut typ = typ.clone();
typ.substitute_variables(&substitutions);
let alias_variable = type_to_variable(subs, rank, pools, arena, self, &typ, false);
(alias_variable, kind)
} else {
if !substitutions.is_empty() {
typ.substitute_variables(&substitutions);
}
@ -389,6 +427,7 @@ impl Aliases {
(alias_variable, kind)
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Env {
@ -487,6 +526,8 @@ impl Pools {
/// What phase in the compiler is reaching out to solve types.
/// This is important to distinguish subtle differences in the behavior of the solving algorithm.
//
// TODO the APIs of this trait suck, this needs a nice cleanup.
pub trait Phase {
/// The regular type-solving phase, or during some later phase of compilation.
/// During the solving phase we must anticipate that some information is still unknown and react to
@ -497,12 +538,23 @@ pub trait Phase {
where
F: FnMut(&AbilitiesStore) -> T;
/// Given a known lambda set's ambient function in an external module, copy that ambient
/// function into the given subs.
fn copy_lambda_set_ambient_function_to_home_subs(
&self,
external_lambda_set_var: Variable,
external_module_id: ModuleId,
home_subs: &mut Subs,
) -> Variable;
/// Find the ambient function var at a given region for an ability member definition (not a
/// specialization!), and copy that into the given subs.
fn get_and_copy_ability_member_ambient_function(
&self,
ability_member: Symbol,
region: u8,
home_subs: &mut Subs,
) -> Variable;
}
struct SolvePhase<'a> {
@ -533,6 +585,35 @@ impl Phase for SolvePhase<'_> {
} = home_subs.get_lambda_set(external_lambda_set_var);
ambient_function
}
fn get_and_copy_ability_member_ambient_function(
&self,
ability_member: Symbol,
region: u8,
home_subs: &mut Subs,
) -> Variable {
// During solving we're only aware of our module's abilities store, the var must
// be in our module store. Even if the specialization lambda set comes from another
// module, we should have taken care to import it before starting solving in this module.
let member_def = self
.abilities_store
.member_def(ability_member)
.unwrap_or_else(|| {
internal_error!(
"{:?} is not resolved, or not an ability member!",
ability_member
)
});
let member_var = member_def.signature_var();
let region_lset = get_member_lambda_sets_at_region(home_subs, member_var, region);
let LambdaSet {
ambient_function, ..
} = home_subs.get_lambda_set(region_lset);
ambient_function
}
}
#[derive(Clone)]
@ -543,6 +624,7 @@ struct State {
#[allow(clippy::too_many_arguments)] // TODO: put params in a context/env var
pub fn run(
home: ModuleId,
constraints: &Constraints,
problems: &mut Vec<TypeError>,
mut subs: Subs,
@ -550,9 +632,11 @@ pub fn run(
constraint: &Constraint,
pending_derives: PendingDerives,
abilities_store: &mut AbilitiesStore,
derived_symbols: GlobalDerivedSymbols,
exposed_by_module: &ExposedByModule,
derived_module: SharedDerivedModule,
) -> (Solved<Subs>, Env) {
let env = run_in_place(
home,
constraints,
problems,
&mut subs,
@ -560,7 +644,8 @@ pub fn run(
constraint,
pending_derives,
abilities_store,
derived_symbols,
exposed_by_module,
derived_module,
);
(Solved(subs), env)
@ -569,6 +654,7 @@ pub fn run(
/// Modify an existing subs in-place instead
#[allow(clippy::too_many_arguments)] // TODO: put params in a context/env var
fn run_in_place(
_home: ModuleId, // TODO: remove me?
constraints: &Constraints,
problems: &mut Vec<TypeError>,
subs: &mut Subs,
@ -576,7 +662,8 @@ fn run_in_place(
constraint: &Constraint,
pending_derives: PendingDerives,
abilities_store: &mut AbilitiesStore,
derived_symbols: GlobalDerivedSymbols,
exposed_by_module: &ExposedByModule,
derived_module: SharedDerivedModule,
) -> Env {
let mut pools = Pools::default();
@ -614,11 +701,12 @@ fn run_in_place(
// are legal, which we need to register.
let new_must_implement = compact_lambda_sets_of_vars(
subs,
&derived_module,
&arena,
&mut pools,
deferred_uls_to_resolve,
&SolvePhase { abilities_store },
&derived_symbols,
exposed_by_module,
);
deferred_obligations.add(new_must_implement, AbilityImplError::IncompleteAbility);
@ -1371,10 +1459,9 @@ fn solve(
subs.commit_snapshot(snapshot);
introduce(subs, rank, pools, &vars);
if !must_implement_ability.is_empty() {
internal_error!("Didn't expect ability vars to land here");
}
deferred_obligations
.add(must_implement_ability, AbilityImplError::IncompleteAbility);
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
// Case 1: unify error types, but don't check exhaustiveness.
@ -1876,11 +1963,12 @@ fn unique_unspecialized_lambda(subs: &Subs, c_a: Variable, uls: &[Uls]) -> Optio
#[must_use]
pub fn compact_lambda_sets_of_vars<P: Phase>(
subs: &mut Subs,
derived_module: &SharedDerivedModule,
arena: &Bump,
pools: &mut Pools,
uls_of_var: UlsOfVar,
phase: &P,
derived_symbols: &GlobalDerivedSymbols,
exposed_by_module: &ExposedByModule,
) -> MustImplementConstraints {
// let mut seen = VecSet::default();
let mut must_implement = MustImplementConstraints::default();
@ -1989,6 +2077,7 @@ pub fn compact_lambda_sets_of_vars<P: Phase>(
ord => ord,
}
});
trace_compact!(2. subs, &uls_a);
// 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`:
@ -2004,8 +2093,16 @@ pub fn compact_lambda_sets_of_vars<P: Phase>(
// continue;
// }
let (new_must_implement, new_uls_of_var) =
compact_lambda_set(subs, arena, pools, c_a, l, phase, derived_symbols);
let (new_must_implement, new_uls_of_var) = compact_lambda_set(
subs,
derived_module,
arena,
pools,
c_a,
l,
phase,
exposed_by_module,
);
must_implement.extend(new_must_implement);
uls_of_var_queue.extend(new_uls_of_var.drain());
@ -2018,14 +2115,16 @@ pub fn compact_lambda_sets_of_vars<P: Phase>(
}
#[must_use]
#[allow(clippy::too_many_arguments)]
fn compact_lambda_set<P: Phase>(
subs: &mut Subs,
derived_module: &SharedDerivedModule,
arena: &Bump,
pools: &mut Pools,
resolved_concrete: Variable,
this_lambda_set: Variable,
phase: &P,
derived_symbols: &GlobalDerivedSymbols,
exposed_by_module: &ExposedByModule,
) -> (MustImplementConstraints, UlsOfVar) {
// 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`:
// 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set.
@ -2071,124 +2170,39 @@ fn compact_lambda_set<P: Phase>(
Content::LambdaSet(t_f1_lambda_set_without_concrete),
);
enum Spec {
// 2. Let `t_f2` be the directly ambient function of the specialization lambda set resolved by `C:f:r`.
Some { t_f2: Variable },
// The specialized lambda set should actually just be dropped, not resolved and unified.
Drop,
}
let specialization_decision = make_specialization_decision(subs, c);
let spec = match subs.get_content_without_compacting(c) {
Content::Structure(_) | Content::Alias(_, _, _, AliasKind::Structural) => {
// This is a structural type, find the name of the derived ability function it
// should use.
match Derived::encoding(subs, c) {
Ok(derived) => {
let specialization_symbol = match derived {
Derived::Immediate(symbol) => symbol,
Derived::Key(derive_key) => {
let mut derived_symbols = derived_symbols.lock().unwrap();
derived_symbols.get_or_insert(derive_key)
let specialization_key = match specialization_decision {
SpecializeDecision::Specialize(key) => key,
SpecializeDecision::Drop => {
// Do nothing other than to remove the concrete lambda to drop from the lambda set,
// which we already did in 1b above.
trace_compact!(3iter_end_skipped. subs, t_f1);
return (Default::default(), Default::default());
}
};
let specialization_symbol_slice =
UnionLabels::insert_into_subs(subs, vec![(specialization_symbol, vec![])]);
// TODO: This is WRONG, fix it!
let ambient_function = Variable::NULL;
let _lambda_set_for_derived = subs.fresh(Descriptor {
content: Content::LambdaSet(subs::LambdaSet {
solved: specialization_symbol_slice,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function,
}),
rank: target_rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
});
Spec::Some {
t_f2: ambient_function,
}
}
Err(DeriveError::UnboundVar) => {
// not specialized yet, but that also means that it can't possibly be derivable
// at this point?
// TODO: is this right? Revisit if it causes us problems in the future.
Spec::Drop
}
Err(DeriveError::Underivable) => {
// we should have reported an error for this; drop the lambda set.
Spec::Drop
}
}
}
Content::Alias(opaque, _, _, AliasKind::Opaque) => {
let opaque_home = opaque.module_id();
let opt_lambda_set =
phase.with_module_abilities_store(opaque_home, |abilities_store| {
let opt_specialization = abilities_store.get_specialization(f, *opaque);
match (P::IS_LATE, opt_specialization) {
(false, None) => {
// doesn't specialize, we'll have reported an error for this
None
}
(true, None) => {
internal_error!(
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
opaque,
f,
);
}
(_, Some(specialization)) => {
let specialized_lambda_set = *specialization
.specialization_lambda_sets
.get(&r)
.unwrap_or_else(|| {
internal_error!(
"lambda set region ({:?}, {}) not resolved",
f,
r
)
});
Some(specialized_lambda_set)
}
}
});
match opt_lambda_set {
Some(lambda_set_var) => {
// Get the ambient function type
let spec_ambient_function = phase
.copy_lambda_set_ambient_function_to_home_subs(
lambda_set_var,
opaque_home,
let specialization_ambient_function_var = get_specialization_lambda_set_ambient_function(
subs,
derived_module,
phase,
f,
r,
specialization_key,
exposed_by_module,
target_rank,
);
Spec::Some {
t_f2: spec_ambient_function,
}
}
None => Spec::Drop,
}
}
Content::Error => Spec::Drop,
Content::FlexAbleVar(..)
| Content::RigidAbleVar(..)
| Content::FlexVar(..)
| Content::RigidVar(..)
| Content::RecursionVar { .. }
| Content::LambdaSet(..)
| Content::RangedNumber(..) => {
internal_error!("unexpected")
let t_f2 = match specialization_ambient_function_var {
Ok(lset) => lset,
Err(()) => {
// Do nothing other than to remove the concrete lambda to drop from the lambda set,
// which we already did in 1b above.
trace_compact!(3iter_end_skipped. subs, t_f1);
return (Default::default(), Default::default());
}
};
match spec {
Spec::Some { t_f2 } => {
// Ensure the specialized ambient function we'll unify with is not a generalized one, but one
// at the rank of the lambda set being compacted.
let t_f2 = deep_copy_var_in(subs, target_rank, pools, t_f2, arena);
@ -2203,11 +2217,141 @@ fn compact_lambda_set<P: Phase>(
(new_must_implement_ability, new_lambda_sets_to_specialize)
}
Spec::Drop => {
// Do nothing other than to remove the concrete lambda to drop from the lambda set,
// which we already did in 1b above.
trace_compact!(3iter_end_skipped. subs, t_f1);
(Default::default(), Default::default())
enum SpecializationTypeKey {
Opaque(Symbol),
Derived(DeriveKey),
Immediate(Symbol),
}
enum SpecializeDecision {
Specialize(SpecializationTypeKey),
Drop,
}
fn make_specialization_decision(subs: &Subs, var: Variable) -> SpecializeDecision {
use Content::*;
use SpecializationTypeKey::*;
match subs.get_content_without_compacting(var) {
Alias(opaque, _, _, AliasKind::Opaque) if opaque.module_id() != ModuleId::NUM => {
SpecializeDecision::Specialize(Opaque(*opaque))
}
Structure(_) | Alias(_, _, _, _) => {
// This is a structural type, find the name of the derived ability function it
// should use.
match roc_derive_key::Derived::encoding(subs, var) {
Ok(derived) => match derived {
roc_derive_key::Derived::Immediate(imm) => {
SpecializeDecision::Specialize(Immediate(imm))
// todo!("deal with lambda set extraction from immediates")
}
roc_derive_key::Derived::Key(derive_key) => {
SpecializeDecision::Specialize(Derived(derive_key))
}
},
Err(DeriveError::UnboundVar) => {
// not specialized yet, but that also means that it can't possibly be derivable
// at this point?
// TODO: is this right? Revisit if it causes us problems in the future.
SpecializeDecision::Drop
}
Err(DeriveError::Underivable) => {
// we should have reported an error for this; drop the lambda set.
SpecializeDecision::Drop
}
}
}
Error => SpecializeDecision::Drop,
FlexAbleVar(_, _)
| RigidAbleVar(..)
| FlexVar(..)
| RigidVar(..)
| RecursionVar { .. }
| LambdaSet(..)
| RangedNumber(..) => {
internal_error!("unexpected")
}
}
}
#[allow(clippy::too_many_arguments)]
fn get_specialization_lambda_set_ambient_function<P: Phase>(
subs: &mut Subs,
derived_module: &SharedDerivedModule,
phase: &P,
ability_member: Symbol,
lset_region: u8,
specialization_key: SpecializationTypeKey,
exposed_by_module: &ExposedByModule,
target_rank: Rank,
) -> Result<Variable, ()> {
match specialization_key {
SpecializationTypeKey::Opaque(opaque) => {
let opaque_home = opaque.module_id();
let external_specialized_lset =
phase.with_module_abilities_store(opaque_home, |abilities_store| {
let opt_specialization =
abilities_store.get_specialization(ability_member, opaque);
match (P::IS_LATE, opt_specialization) {
(false, None) => {
// doesn't specialize, we'll have reported an error for this
Err(())
}
(true, None) => {
internal_error!(
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
opaque,
ability_member,
);
}
(_, Some(specialization)) => {
let specialized_lambda_set = *specialization
.specialization_lambda_sets
.get(&lset_region)
.expect("lambda set region not resolved");
Ok(specialized_lambda_set)
}
}
})?;
let specialized_ambient = phase.copy_lambda_set_ambient_function_to_home_subs(
external_specialized_lset,
opaque_home,
subs,
);
Ok(specialized_ambient)
}
SpecializationTypeKey::Derived(derive_key) => {
let mut derived_module = derived_module.lock().unwrap();
let (_, _, specialization_lambda_sets) =
derived_module.get_or_insert(exposed_by_module, derive_key);
let specialized_lambda_set = *specialization_lambda_sets
.get(&lset_region)
.expect("lambda set region not resolved");
let specialized_ambient = derived_module.copy_lambda_set_ambient_function_to_subs(
specialized_lambda_set,
subs,
target_rank,
);
Ok(specialized_ambient)
}
SpecializationTypeKey::Immediate(imm) => {
// Immediates are like opaques in that we can simply look up their type definition in
// the ability store, there is nothing new to synthesize.
//
// THEORY: if something can become an immediate, it will always be available in the
// local ability store, because the transformation is local (?)
let immediate_lambda_set_at_region =
phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs);
Ok(immediate_lambda_set_at_region)
}
}
}

View file

@ -22,12 +22,12 @@ use roc_can::{
use roc_collections::VecSet;
use roc_constrain::expr::constrain_decls;
use roc_debug_flags::dbg_do;
use roc_derive::{synth_var, DerivedModule, StolenFromDerived};
use roc_derive::{synth_var, DerivedModule};
use roc_derive_key::{DeriveKey, Derived};
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
use roc_module::{
ident::TagName,
symbol::{Interns, ModuleId, Symbol},
symbol::{IdentIds, Interns, ModuleId, Symbol},
};
use roc_region::all::LineInfo;
use roc_reporting::report::{type_problem, RocDocAllocator};
@ -40,7 +40,7 @@ use roc_types::{
types::{AliasKind, RecordField},
};
const DERIVED_MODULE: ModuleId = ModuleId::DERIVED;
const DERIVED_MODULE: ModuleId = ModuleId::DERIVED_SYNTH;
fn encode_path() -> PathBuf {
let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?");
@ -145,7 +145,7 @@ fn check_derived_typechecks_and_golden(
test_module,
&mut test_subs,
pending_abilities,
exposed_for_module,
&exposed_for_module,
&mut def_types,
&mut rigid_vars,
);
@ -158,6 +158,7 @@ fn check_derived_typechecks_and_golden(
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1")
);
let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve(
test_module,
&constraints,
constr,
RigidVariables::default(),
@ -165,6 +166,7 @@ fn check_derived_typechecks_and_golden(
default_aliases(),
abilities_store,
Default::default(),
&exposed_for_module.exposed_by_module,
Default::default(),
);
let subs = solved_subs.inner_mut();
@ -242,12 +244,12 @@ where
)
.unwrap();
let mut derived_module = DerivedModule::default();
let mut subs = Subs::new();
let ident_ids = IdentIds::default();
let source_var = synth_input(&mut subs);
let key = get_key(&subs, source_var);
let mut stolen = derived_module.steal();
let source_var = synth_input(&mut stolen.subs);
let key = get_key(&stolen.subs, source_var);
derived_module.return_stolen(stolen);
let mut derived_module = unsafe { DerivedModule::from_components(subs, ident_ids) };
let mut exposed_by_module = ExposedByModule::default();
exposed_by_module.insert(
@ -263,7 +265,7 @@ where
let specialization_lsets = specialization_lsets.clone();
let derived_def = derived_def.clone();
let StolenFromDerived { ident_ids, subs } = derived_module.steal();
let (subs, ident_ids) = derived_module.decompose();
interns.all_ident_ids.insert(DERIVED_MODULE, ident_ids);
DERIVED_MODULE.register_debug_idents(interns.all_ident_ids.get(&DERIVED_MODULE).unwrap());
@ -513,25 +515,7 @@ fn immediates() {
check_immediate(v!(DEC), Symbol::ENCODE_DEC);
check_immediate(v!(F32), Symbol::ENCODE_F32);
check_immediate(v!(F64), Symbol::ENCODE_F64);
}
#[test]
fn string() {
derive_test(v!(STR), |golden| {
assert_snapshot!(golden, @r###"
# derived for Str
# Str -[[toEncoder_string(0)]]-> Encoder fmt | fmt has EncoderFormatting
# Str -[[toEncoder_string(0)]]-> (List U8, fmt -[[custom(2) Str]]-> List U8) | fmt has EncoderFormatting
# Specialization lambda sets:
# @<1>: [[toEncoder_string(0)]]
# @<2>: [[custom(2) Str]]
#Derived.toEncoder_string =
\#Derived.s ->
Encode.custom \#Derived.bytes, #Derived.fmt ->
Encode.appendWith #Derived.bytes (Encode.string #Derived.s) #Derived.fmt
"###
)
})
check_immediate(v!(STR), Symbol::ENCODE_STRING);
}
#[test]

View file

@ -375,3 +375,267 @@ fn encode_use_stdlib_without_wrapping_custom() {
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn to_encoder_encode_custom_has_capture() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
HelloWorld := Str
toEncoder = \@HelloWorld s1 ->
Encode.custom \bytes, fmt ->
bytes
|> Encode.appendWith (Encode.string s1) fmt
main =
result = Str.fromUtf8 (Encode.toBytes (@HelloWorld "Hello, World!\n") Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("\"Hello, World!\n\""),
RocStr
)
}
mod encode_immediate {
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use indoc::indoc;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocStr;
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn string() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode.{ toEncoder }, Json] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes "foo" Json.format) is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("\"foo\""),
RocStr
)
}
macro_rules! num_immediate {
($($num:expr, $typ:ident)*) => {$(
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn $typ() {
assert_evals_to!(
&format!(indoc!(
r#"
app "test" imports [Encode.{{ toEncoder }}, Json] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes {}{} Json.format) is
Ok s -> s
_ -> "<bad>"
"#
), $num, stringify!($typ)),
RocStr::from(format!(r#"{}"#, $num).as_str()),
RocStr
)
}
)*}
}
num_immediate! {
17, i8
17, i16
17, i32
17, i64
17, i128
17, u8
17, u16
17, u32
17, u64
17, u128
// 17.23, f32 TODO https://github.com/rtfeldman/roc/issues/3522
17.23, f64
// 17.23, dec TODO https://github.com/rtfeldman/roc/issues/3522
}
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_derived_record_one_field_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
result = Str.fromUtf8 (Encode.toBytes {a: "foo"} Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":"foo",}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_derived_record_two_fields_strings() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
rcd = {a: "foo", b: "bar"}
result = Str.fromUtf8 (Encode.toBytes rcd Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":"foo","b":"bar",}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_derived_nested_record_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
rcd = {a: {b: "bar"}}
encoded = Encode.toBytes rcd Json.format
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":{"b":"bar",},}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_derived_tag_one_payload_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
x : [A Str]
x = A "foo"
result = Str.fromUtf8 (Encode.toBytes x Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"A":["foo",]}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_derived_tag_two_payloads_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
x : [A Str Str]
x = A "foo" "bar"
result = Str.fromUtf8 (Encode.toBytes x Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"A":["foo","bar",]}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_derived_nested_tag_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
x : [A [B Str Str]]
x = A (B "foo" "bar")
encoded = Encode.toBytes x Json.format
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"A":[{"B":["foo","bar",]},]}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_derived_nested_record_tag_record() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
x : {a: [B {c: Str}]}
x = {a: (B ({c: "foo"}))}
encoded = Encode.toBytes x Json.format
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":{"B":[{"c":"foo",},]},}"#),
RocStr
)
}

View file

@ -1,4 +1,4 @@
#![cfg(feature = "gen-llvm")]
#![cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
@ -6,8 +6,8 @@ use crate::helpers::llvm::assert_evals_to;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to;
// #[cfg(feature = "gen-wasm")]
// use crate::helpers::wasm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
use indoc::indoc;
use roc_std::{RocList, RocStr};
@ -32,7 +32,8 @@ fn dict_insert_empty() {
assert_evals_to!(
indoc!(
r#"
Dict.insert Dict.empty 42 32
Dict.empty
|> Dict.insert 42 32
|> Dict.len
"#
),
@ -47,7 +48,7 @@ fn dict_empty_contains() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty : Dict.Dict I64 F64
empty = Dict.empty
Dict.contains empty 42
@ -64,7 +65,7 @@ fn dict_nonempty_contains() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty : Dict.Dict I64 F64
empty = Dict.insert Dict.empty 42 1.23
Dict.contains empty 42
@ -81,7 +82,7 @@ fn dict_empty_remove() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty : Dict.Dict I64 F64
empty = Dict.empty
empty
@ -100,7 +101,7 @@ fn dict_nonempty_remove() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty : Dict.Dict I64 F64
empty = Dict.insert Dict.empty 42 1.23
empty
@ -119,7 +120,7 @@ fn dict_nonempty_get() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty : Dict.Dict I64 F64
empty = Dict.insert Dict.empty 42 1.23
withDefault = \x, def ->
@ -157,12 +158,12 @@ fn dict_nonempty_get() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict : Dict.Dict I64 I64
myDict =
Dict.empty
|> Dict.insert 0 100
@ -179,12 +180,12 @@ fn keys() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict : Dict.Dict I64 I64
myDict =
Dict.empty
|> Dict.insert 0 100
@ -201,12 +202,12 @@ fn values() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn from_list_with_fold_simple() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict : Dict.Dict I64 I64
myDict =
[1,2,3]
|> List.walk Dict.empty (\accum, value -> Dict.insert accum value value)
@ -214,13 +215,13 @@ fn from_list_with_fold_simple() {
Dict.values myDict
"#
),
RocList::from_slice(&[2, 3, 1]),
RocList::from_slice(&[1, 2, 3]),
RocList<i64>
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn from_list_with_fold_reallocates() {
assert_evals_to!(
indoc!(
@ -232,7 +233,7 @@ fn from_list_with_fold_reallocates() {
else
accum
myDict : Dict I64 I64
myDict : Dict.Dict I64 I64
myDict =
# 25 elements (8 + 16 + 1) is guaranteed to overflow/reallocate at least twice
range 0 25 []
@ -242,20 +243,20 @@ fn from_list_with_fold_reallocates() {
"#
),
RocList::from_slice(&[
4, 5, 20, 0, 7, 3, 1, 21, 10, 6, 13, 9, 14, 19, 2, 15, 12, 17, 16, 18, 22, 8, 11, 24,
23
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24
]),
RocList<i64>
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn small_str_keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict Str I64
myDict : Dict.Dict Str I64
myDict =
Dict.empty
|> Dict.insert "a" 100
@ -266,18 +267,18 @@ fn small_str_keys() {
Dict.keys myDict
"#
),
RocList::from_slice(&[RocStr::from("c"), RocStr::from("a"), RocStr::from("b"),],),
RocList::from_slice(&["a".into(), "b".into(), "c".into(),],),
RocList<RocStr>
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn big_str_keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict Str I64
myDict : Dict.Dict Str I64
myDict =
Dict.empty
|> Dict.insert "Leverage agile frameworks to provide a robust" 100
@ -289,20 +290,20 @@ fn big_str_keys() {
),
RocList::from_slice(&[
RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
]),
RocList<RocStr>
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn big_str_values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 Str
myDict : Dict.Dict I64 Str
myDict =
Dict.empty
|> Dict.insert 100 "Leverage agile frameworks to provide a robust"
@ -314,8 +315,8 @@ fn big_str_values() {
),
RocList::from_slice(&[
RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
]),
RocList<RocStr>
);
@ -327,7 +328,7 @@ fn unit_values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 {}
myDict : Dict.Dict I64 {}
myDict =
Dict.empty
|> Dict.insert 0 {}
@ -349,7 +350,7 @@ fn single() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 {}
myDict : Dict.Dict I64 {}
myDict =
Dict.single 0 {}
@ -363,13 +364,13 @@ fn single() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn union() {
fn insert_all() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 {}
myDict =
Dict.union (Dict.single 0 {}) (Dict.single 1 {})
Dict.insertAll (Dict.single 0 {}) (Dict.single 1 {})
Dict.len myDict
"#
@ -380,14 +381,15 @@ fn union() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn union_prefer_first() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn insert_all_prefer_first() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict : Dict.Dict I64 I64
myDict =
Dict.union (Dict.single 0 100) (Dict.single 0 200)
(Dict.single 0 100)
|> Dict.insertAll (Dict.single 0 200)
Dict.values myDict
"#
@ -399,11 +401,11 @@ fn union_prefer_first() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn intersection() {
fn keep_shared() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 {}
dict1 : Dict.Dict I64 {}
dict1 =
Dict.empty
|> Dict.insert 1 {}
@ -412,14 +414,14 @@ fn intersection() {
|> Dict.insert 4 {}
|> Dict.insert 5 {}
dict2 : Dict I64 {}
dict2 : Dict.Dict I64 {}
dict2 =
Dict.empty
|> Dict.insert 0 {}
|> Dict.insert 2 {}
|> Dict.insert 4 {}
Dict.intersection dict1 dict2
Dict.keepShared dict1 dict2
|> Dict.len
"#
),
@ -429,12 +431,12 @@ fn intersection() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn intersection_prefer_first() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn keep_shared_prefer_first() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 I64
dict1 : Dict.Dict I64 I64
dict1 =
Dict.empty
|> Dict.insert 1 1
@ -443,29 +445,29 @@ fn intersection_prefer_first() {
|> Dict.insert 4 4
|> Dict.insert 5 5
dict2 : Dict I64 I64
dict2 : Dict.Dict I64 I64
dict2 =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 2 200
|> Dict.insert 4 300
Dict.intersection dict1 dict2
Dict.keepShared dict1 dict2
|> Dict.values
"#
),
RocList::from_slice(&[4, 2]),
RocList::from_slice(&[2, 4]),
RocList<i64>
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn difference() {
fn remove_all() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 {}
dict1 : Dict.Dict I64 {}
dict1 =
Dict.empty
|> Dict.insert 1 {}
@ -474,14 +476,14 @@ fn difference() {
|> Dict.insert 4 {}
|> Dict.insert 5 {}
dict2 : Dict I64 {}
dict2 : Dict.Dict I64 {}
dict2 =
Dict.empty
|> Dict.insert 0 {}
|> Dict.insert 2 {}
|> Dict.insert 4 {}
Dict.difference dict1 dict2
Dict.removeAll dict1 dict2
|> Dict.len
"#
),
@ -492,11 +494,11 @@ fn difference() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn difference_prefer_first() {
fn remove_all_prefer_first() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 I64
dict1 : Dict.Dict I64 I64
dict1 =
Dict.empty
|> Dict.insert 1 1
@ -505,18 +507,18 @@ fn difference_prefer_first() {
|> Dict.insert 4 4
|> Dict.insert 5 5
dict2 : Dict I64 I64
dict2 : Dict.Dict I64 I64
dict2 =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 2 200
|> Dict.insert 4 300
Dict.difference dict1 dict2
Dict.removeAll dict1 dict2
|> Dict.values
"#
),
RocList::from_slice(&[5, 3, 1]),
RocList::from_slice(&[1, 5, 3]),
RocList<i64>
);
}
@ -527,7 +529,7 @@ fn walk_sum_keys() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 I64
dict1 : Dict.Dict I64 I64
dict1 =
Dict.empty
|> Dict.insert 1 1

View file

@ -117,17 +117,17 @@ fn union() {
assert_evals_to!(
indoc!(
r#"
set1 : Set I64
set1 : Set.Set I64
set1 = Set.fromList [1,2]
set2 : Set I64
set2 : Set.Set I64
set2 = Set.fromList [1,3,4]
Set.union set1 set2
|> Set.toList
"#
),
RocList::from_slice(&[4, 2, 3, 1]),
RocList::from_slice(&[1, 2, 3, 4]),
RocList<i64>
);
}
@ -138,10 +138,10 @@ fn difference() {
assert_evals_to!(
indoc!(
r#"
set1 : Set I64
set1 : Set.Set I64
set1 = Set.fromList [1,2]
set2 : Set I64
set2 : Set.Set I64
set2 = Set.fromList [1,3,4]
Set.difference set1 set2
@ -159,10 +159,10 @@ fn intersection() {
assert_evals_to!(
indoc!(
r#"
set1 : Set I64
set1 : Set.Set I64
set1 = Set.fromList [1,2]
set2 : Set I64
set2 : Set.Set I64
set2 = Set.fromList [1,3,4]
Set.intersection set1 set2
@ -223,7 +223,7 @@ fn from_list() {
|> Set.toList
"#
),
RocList::from_slice(&[4, 2, 3, 1]),
RocList::from_slice(&[1, 2, 3, 4]),
RocList<i64>
);
@ -244,6 +244,7 @@ fn from_list() {
}
#[test]
#[ignore]
#[cfg(any(feature = "gen-llvm"))]
fn from_list_void() {
assert_evals_to!(

View file

@ -228,6 +228,7 @@ fn create_llvm_module<'a>(
let (main_fn_name, main_fn) = match config.mode {
LlvmBackendMode::Binary => unreachable!(),
LlvmBackendMode::CliTest => unreachable!(),
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
&env,
config.opt_level,

View file

@ -1,13 +1,17 @@
procedure Dict.1 ():
let Dict.26 : Dict [] [] = lowlevel DictEmpty ;
ret Dict.26;
let Dict.102 : List {[], []} = Array [];
ret Dict.102;
procedure Dict.7 (#Attr.2):
let Dict.25 : U64 = lowlevel DictSize #Attr.2;
dec #Attr.2;
ret Dict.25;
procedure Dict.7 (Dict.96):
let Dict.101 : U64 = CallByName List.6 Dict.96;
ret Dict.101;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure Test.0 ():
let Test.2 : Dict [] [] = CallByName Dict.1;
let Test.2 : List {[], []} = CallByName Dict.1;
let Test.1 : U64 = CallByName Dict.7 Test.2;
dec Test.2;
ret Test.1;

View file

@ -0,0 +1,444 @@
procedure #Derived.0 (#Derived.1):
let #Derived_gen.1 : {Str} = Struct {#Derived.1};
let #Derived_gen.0 : {Str} = CallByName Encode.22 #Derived_gen.1;
ret #Derived_gen.0;
procedure #Derived.2 (#Derived.3, #Derived.4, #Attr.12):
let #Derived.1 : Str = StructAtIndex 0 #Attr.12;
inc #Derived.1;
dec #Attr.12;
let #Derived_gen.7 : Str = "a";
let #Derived_gen.8 : {Str} = CallByName #Derived.5 #Derived.1;
let #Derived_gen.6 : {Str, {Str}} = Struct {#Derived_gen.7, #Derived_gen.8};
let #Derived_gen.5 : List {Str, {Str}} = Array [#Derived_gen.6];
let #Derived_gen.4 : {List {Str, {Str}}} = CallByName Json.19 #Derived_gen.5;
let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4;
ret #Derived_gen.3;
procedure #Derived.5 (#Derived.6):
let #Derived_gen.15 : {Str} = Struct {#Derived.6};
let #Derived_gen.14 : {Str} = CallByName Encode.22 #Derived_gen.15;
ret #Derived_gen.14;
procedure #Derived.7 (#Derived.8, #Derived.9, #Attr.12):
let #Derived.6 : Str = StructAtIndex 0 #Attr.12;
inc #Derived.6;
dec #Attr.12;
let #Derived_gen.21 : Str = "b";
let #Derived_gen.22 : {Str} = CallByName Json.17 #Derived.6;
let #Derived_gen.20 : {Str, {Str}} = Struct {#Derived_gen.21, #Derived_gen.22};
let #Derived_gen.19 : List {Str, {Str}} = Array [#Derived_gen.20];
let #Derived_gen.18 : {List {Str, {Str}}} = CallByName Json.19 #Derived_gen.19;
let #Derived_gen.17 : List U8 = CallByName Encode.23 #Derived.8 #Derived_gen.18 #Derived.9;
ret #Derived_gen.17;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.106 : List U8 = CallByName #Derived.2 Encode.94 Encode.96 Encode.102;
ret Encode.106;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.113 : List U8 = CallByName Json.77 Encode.94 Encode.96 Encode.102;
ret Encode.113;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.115 : List U8 = CallByName #Derived.7 Encode.94 Encode.96 Encode.102;
ret Encode.115;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.125 : List U8 = CallByName Json.77 Encode.94 Encode.96 Encode.102;
ret Encode.125;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.128 : List U8 = CallByName Json.65 Encode.94 Encode.96 Encode.102;
ret Encode.128;
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {Str} = CallByName #Derived.0 Encode.100;
let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101;
ret Encode.103;
procedure Json.1 ():
let Json.102 : {} = Struct {};
ret Json.102;
procedure Json.17 (Json.64):
let Json.149 : {Str} = Struct {Json.64};
let Json.148 : {Str} = CallByName Encode.22 Json.149;
ret Json.148;
procedure Json.19 (Json.76):
let Json.104 : {List {Str, {Str}}} = Struct {Json.76};
let Json.103 : {List {Str, {Str}}} = CallByName Encode.22 Json.104;
ret Json.103;
procedure Json.19 (Json.76):
let Json.146 : {List {Str, {Str}}} = Struct {Json.76};
let Json.145 : {List {Str, {Str}}} = CallByName Encode.22 Json.146;
ret Json.145;
procedure Json.65 (Json.66, Json.150, #Attr.12):
let Json.64 : Str = StructAtIndex 0 #Attr.12;
inc Json.64;
dec #Attr.12;
let Json.190 : I32 = 34i64;
let Json.189 : U8 = CallByName Num.123 Json.190;
let Json.187 : List U8 = CallByName List.4 Json.66 Json.189;
let Json.188 : List U8 = CallByName Str.12 Json.64;
let Json.184 : List U8 = CallByName List.8 Json.187 Json.188;
let Json.186 : I32 = 34i64;
let Json.185 : U8 = CallByName Num.123 Json.186;
let Json.183 : List U8 = CallByName List.4 Json.184 Json.185;
ret Json.183;
procedure Json.77 (Json.78, Json.105, #Attr.12):
let Json.76 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.76;
dec #Attr.12;
let Json.138 : I32 = 123i64;
let Json.137 : U8 = CallByName Num.123 Json.138;
let Json.80 : List U8 = CallByName List.4 Json.78 Json.137;
let Json.136 : U64 = CallByName List.6 Json.76;
let Json.113 : {List U8, U64} = Struct {Json.80, Json.136};
let Json.114 : {} = Struct {};
let Json.112 : {List U8, U64} = CallByName List.18 Json.76 Json.113 Json.114;
dec Json.76;
let Json.82 : List U8 = StructAtIndex 0 Json.112;
inc Json.82;
dec Json.112;
let Json.111 : I32 = 125i64;
let Json.110 : U8 = CallByName Num.123 Json.111;
let Json.109 : List U8 = CallByName List.4 Json.82 Json.110;
ret Json.109;
procedure Json.77 (Json.78, Json.105, #Attr.12):
let Json.76 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.76;
dec #Attr.12;
let Json.181 : I32 = 123i64;
let Json.180 : U8 = CallByName Num.123 Json.181;
let Json.80 : List U8 = CallByName List.4 Json.78 Json.180;
let Json.179 : U64 = CallByName List.6 Json.76;
let Json.156 : {List U8, U64} = Struct {Json.80, Json.179};
let Json.157 : {} = Struct {};
let Json.155 : {List U8, U64} = CallByName List.18 Json.76 Json.156 Json.157;
dec Json.76;
let Json.82 : List U8 = StructAtIndex 0 Json.155;
inc Json.82;
dec Json.155;
let Json.154 : I32 = 125i64;
let Json.153 : U8 = CallByName Num.123 Json.154;
let Json.152 : List U8 = CallByName List.4 Json.82 Json.153;
ret Json.152;
procedure Json.79 (Json.107, Json.108):
let Json.85 : Str = StructAtIndex 0 Json.108;
inc Json.85;
let Json.86 : {Str} = StructAtIndex 1 Json.108;
inc Json.86;
dec Json.108;
let Json.83 : List U8 = StructAtIndex 0 Json.107;
inc Json.83;
let Json.84 : U64 = StructAtIndex 1 Json.107;
dec Json.107;
let Json.135 : I32 = 34i64;
let Json.134 : U8 = CallByName Num.123 Json.135;
let Json.132 : List U8 = CallByName List.4 Json.83 Json.134;
let Json.133 : List U8 = CallByName Str.12 Json.85;
let Json.129 : List U8 = CallByName List.8 Json.132 Json.133;
let Json.131 : I32 = 34i64;
let Json.130 : U8 = CallByName Num.123 Json.131;
let Json.126 : List U8 = CallByName List.4 Json.129 Json.130;
let Json.128 : I32 = 58i64;
let Json.127 : U8 = CallByName Num.123 Json.128;
let Json.124 : List U8 = CallByName List.4 Json.126 Json.127;
let Json.125 : {} = Struct {};
let Json.87 : List U8 = CallByName Encode.23 Json.124 Json.86 Json.125;
joinpoint Json.119 Json.88:
let Json.117 : U64 = 1i64;
let Json.116 : U64 = CallByName Num.20 Json.84 Json.117;
let Json.115 : {List U8, U64} = Struct {Json.88, Json.116};
ret Json.115;
in
let Json.123 : U64 = 0i64;
let Json.120 : Int1 = CallByName Num.24 Json.84 Json.123;
if Json.120 then
let Json.122 : I32 = 44i64;
let Json.121 : U8 = CallByName Num.123 Json.122;
let Json.118 : List U8 = CallByName List.4 Json.87 Json.121;
jump Json.119 Json.118;
else
jump Json.119 Json.87;
procedure Json.79 (Json.107, Json.108):
let Json.85 : Str = StructAtIndex 0 Json.108;
inc Json.85;
let Json.86 : {Str} = StructAtIndex 1 Json.108;
inc Json.86;
dec Json.108;
let Json.83 : List U8 = StructAtIndex 0 Json.107;
inc Json.83;
let Json.84 : U64 = StructAtIndex 1 Json.107;
dec Json.107;
let Json.178 : I32 = 34i64;
let Json.177 : U8 = CallByName Num.123 Json.178;
let Json.175 : List U8 = CallByName List.4 Json.83 Json.177;
let Json.176 : List U8 = CallByName Str.12 Json.85;
let Json.172 : List U8 = CallByName List.8 Json.175 Json.176;
let Json.174 : I32 = 34i64;
let Json.173 : U8 = CallByName Num.123 Json.174;
let Json.169 : List U8 = CallByName List.4 Json.172 Json.173;
let Json.171 : I32 = 58i64;
let Json.170 : U8 = CallByName Num.123 Json.171;
let Json.167 : List U8 = CallByName List.4 Json.169 Json.170;
let Json.168 : {} = Struct {};
let Json.87 : List U8 = CallByName Encode.23 Json.167 Json.86 Json.168;
joinpoint Json.162 Json.88:
let Json.160 : U64 = 1i64;
let Json.159 : U64 = CallByName Num.20 Json.84 Json.160;
let Json.158 : {List U8, U64} = Struct {Json.88, Json.159};
ret Json.158;
in
let Json.166 : U64 = 0i64;
let Json.163 : Int1 = CallByName Num.24 Json.84 Json.166;
if Json.163 then
let Json.165 : I32 = 44i64;
let Json.164 : U8 = CallByName Num.123 Json.165;
let Json.161 : List U8 = CallByName List.4 Json.87 Json.164;
jump Json.162 Json.161;
else
jump Json.162 Json.87;
procedure List.122 (List.123, List.124, #Attr.12):
let List.121 : {} = StructAtIndex 0 #Attr.12;
let List.344 : {List U8, U64} = CallByName Json.79 List.123 List.124;
let List.343 : [C [], C {List U8, U64}] = TagId(1) List.344;
ret List.343;
procedure List.122 (List.123, List.124, #Attr.12):
let List.121 : {} = StructAtIndex 0 #Attr.12;
let List.425 : {List U8, U64} = CallByName Json.79 List.123 List.124;
let List.424 : [C [], C {List U8, U64}] = TagId(1) List.425;
ret List.424;
procedure List.18 (List.119, List.120, List.121):
let List.321 : {{}} = Struct {List.121};
let List.315 : [C [], C {List U8, U64}] = CallByName List.63 List.119 List.120 List.321;
let List.318 : U8 = 1i64;
let List.319 : U8 = GetTagId List.315;
let List.320 : Int1 = lowlevel Eq List.318 List.319;
if List.320 then
let List.126 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.315;
inc List.126;
dec List.315;
ret List.126;
else
let List.127 : [] = UnionAtIndex (Id 0) (Index 0) List.315;
dec List.315;
let List.317 : {List U8, U64} = CallByName List.64 List.127;
ret List.317;
procedure List.18 (List.119, List.120, List.121):
let List.401 : {{}} = Struct {List.121};
let List.395 : [C [], C {List U8, U64}] = CallByName List.63 List.119 List.120 List.401;
let List.398 : U8 = 1i64;
let List.399 : U8 = GetTagId List.395;
let List.400 : Int1 = lowlevel Eq List.398 List.399;
if List.400 then
let List.126 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.395;
inc List.126;
dec List.395;
ret List.126;
else
let List.127 : [] = UnionAtIndex (Id 0) (Index 0) List.395;
dec List.395;
let List.397 : {List U8, U64} = CallByName List.64 List.127;
ret List.397;
procedure List.4 (List.90, List.91):
let List.394 : U64 = 1i64;
let List.393 : List U8 = CallByName List.65 List.90 List.394;
let List.392 : List U8 = CallByName List.66 List.393 List.91;
ret List.392;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure List.6 (#Attr.2):
let List.323 : U64 = lowlevel ListLen #Attr.2;
ret List.323;
procedure List.6 (#Attr.2):
let List.404 : U64 = lowlevel ListLen #Attr.2;
ret List.404;
procedure List.60 (#Attr.2, #Attr.3):
let List.342 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.342;
procedure List.60 (#Attr.2, #Attr.3):
let List.423 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.423;
procedure List.63 (List.283, List.284, List.285):
let List.328 : U64 = 0i64;
let List.329 : U64 = CallByName List.6 List.283;
let List.327 : [C [], C {List U8, U64}] = CallByName List.77 List.283 List.284 List.285 List.328 List.329;
ret List.327;
procedure List.63 (List.283, List.284, List.285):
let List.409 : U64 = 0i64;
let List.410 : U64 = CallByName List.6 List.283;
let List.408 : [C [], C {List U8, U64}] = CallByName List.77 List.283 List.284 List.285 List.409 List.410;
ret List.408;
procedure List.64 (#Attr.2):
let List.407 : {List U8, U64} = lowlevel Unreachable #Attr.2;
ret List.407;
procedure List.65 (#Attr.2, #Attr.3):
let List.406 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.406;
procedure List.66 (#Attr.2, #Attr.3):
let List.405 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.405;
procedure List.77 (List.361, List.362, List.363, List.364, List.365):
joinpoint List.330 List.286 List.287 List.288 List.289 List.290:
let List.332 : Int1 = CallByName Num.22 List.289 List.290;
if List.332 then
let List.341 : {Str, {Str}} = CallByName List.60 List.286 List.289;
let List.333 : [C [], C {List U8, U64}] = CallByName List.122 List.287 List.341 List.288;
let List.338 : U8 = 1i64;
let List.339 : U8 = GetTagId List.333;
let List.340 : Int1 = lowlevel Eq List.338 List.339;
if List.340 then
let List.291 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.333;
inc List.291;
dec List.333;
let List.336 : U64 = 1i64;
let List.335 : U64 = CallByName Num.19 List.289 List.336;
jump List.330 List.286 List.291 List.288 List.335 List.290;
else
let List.292 : [] = UnionAtIndex (Id 0) (Index 0) List.333;
dec List.333;
let List.337 : [C [], C {List U8, U64}] = TagId(0) List.292;
ret List.337;
else
let List.331 : [C [], C {List U8, U64}] = TagId(1) List.287;
ret List.331;
in
jump List.330 List.361 List.362 List.363 List.364 List.365;
procedure List.77 (List.442, List.443, List.444, List.445, List.446):
joinpoint List.411 List.286 List.287 List.288 List.289 List.290:
let List.413 : Int1 = CallByName Num.22 List.289 List.290;
if List.413 then
let List.422 : {Str, {Str}} = CallByName List.60 List.286 List.289;
let List.414 : [C [], C {List U8, U64}] = CallByName List.122 List.287 List.422 List.288;
let List.419 : U8 = 1i64;
let List.420 : U8 = GetTagId List.414;
let List.421 : Int1 = lowlevel Eq List.419 List.420;
if List.421 then
let List.291 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.414;
inc List.291;
dec List.414;
let List.417 : U64 = 1i64;
let List.416 : U64 = CallByName Num.19 List.289 List.417;
jump List.411 List.286 List.291 List.288 List.416 List.290;
else
let List.292 : [] = UnionAtIndex (Id 0) (Index 0) List.414;
dec List.414;
let List.418 : [C [], C {List U8, U64}] = TagId(0) List.292;
ret List.418;
else
let List.412 : [C [], C {List U8, U64}] = TagId(1) List.287;
ret List.412;
in
jump List.411 List.442 List.443 List.444 List.445 List.446;
procedure List.8 (#Attr.2, #Attr.3):
let List.403 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.403;
procedure Num.123 (#Attr.2):
let Num.283 : U8 = lowlevel NumIntCast #Attr.2;
ret Num.283;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.286 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.286;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.284 : U64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.284;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.287 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.287;
procedure Num.24 (#Attr.2, #Attr.3):
let Num.285 : Int1 = lowlevel NumGt #Attr.2 #Attr.3;
ret Num.285;
procedure Str.12 (#Attr.2):
let Str.212 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.212;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.204 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.204;
procedure Str.9 (Str.69):
let Str.202 : U64 = 0i64;
let Str.203 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.202 Str.203;
let Str.199 : Int1 = StructAtIndex 2 Str.70;
if Str.199 then
let Str.201 : Str = StructAtIndex 1 Str.70;
inc Str.201;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
dec Str.70;
let Str.196 : {U64, U8} = Struct {Str.198, Str.197};
let Str.195 : [C {U64, U8}, C Str] = TagId(0) Str.196;
ret Str.195;
procedure Test.0 ():
let Test.12 : Str = "bar";
let Test.10 : {} = CallByName Json.1;
let Test.8 : List U8 = CallByName Encode.25 Test.12 Test.10;
let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8;
let Test.5 : U8 = 1i64;
let Test.6 : U8 = GetTagId Test.1;
let Test.7 : Int1 = lowlevel Eq Test.5 Test.6;
if Test.7 then
let Test.2 : Str = UnionAtIndex (Id 1) (Index 0) Test.1;
inc Test.2;
dec Test.1;
ret Test.2;
else
dec Test.1;
let Test.4 : Str = "<bad>";
ret Test.4;

View file

@ -0,0 +1,285 @@
procedure #Derived.0 (#Derived.1):
let #Derived_gen.1 : {Str} = Struct {#Derived.1};
let #Derived_gen.0 : {Str} = CallByName Encode.22 #Derived_gen.1;
ret #Derived_gen.0;
procedure #Derived.2 (#Derived.3, #Derived.4, #Attr.12):
let #Derived.1 : Str = StructAtIndex 0 #Attr.12;
inc #Derived.1;
dec #Attr.12;
let #Derived_gen.7 : Str = "a";
let #Derived_gen.8 : {Str} = CallByName Json.17 #Derived.1;
let #Derived_gen.6 : {Str, {Str}} = Struct {#Derived_gen.7, #Derived_gen.8};
let #Derived_gen.5 : List {Str, {Str}} = Array [#Derived_gen.6];
let #Derived_gen.4 : {List {Str, {Str}}} = CallByName Json.19 #Derived_gen.5;
let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4;
ret #Derived_gen.3;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.106 : List U8 = CallByName #Derived.2 Encode.94 Encode.96 Encode.102;
ret Encode.106;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.113 : List U8 = CallByName Json.77 Encode.94 Encode.96 Encode.102;
ret Encode.113;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.116 : List U8 = CallByName Json.65 Encode.94 Encode.96 Encode.102;
ret Encode.116;
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {Str} = CallByName #Derived.0 Encode.100;
let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101;
ret Encode.103;
procedure Json.1 ():
let Json.102 : {} = Struct {};
ret Json.102;
procedure Json.17 (Json.64):
let Json.107 : {Str} = Struct {Json.64};
let Json.106 : {Str} = CallByName Encode.22 Json.107;
ret Json.106;
procedure Json.19 (Json.76):
let Json.104 : {List {Str, {Str}}} = Struct {Json.76};
let Json.103 : {List {Str, {Str}}} = CallByName Encode.22 Json.104;
ret Json.103;
procedure Json.65 (Json.66, Json.108, #Attr.12):
let Json.64 : Str = StructAtIndex 0 #Attr.12;
inc Json.64;
dec #Attr.12;
let Json.150 : I32 = 34i64;
let Json.149 : U8 = CallByName Num.123 Json.150;
let Json.147 : List U8 = CallByName List.4 Json.66 Json.149;
let Json.148 : List U8 = CallByName Str.12 Json.64;
let Json.144 : List U8 = CallByName List.8 Json.147 Json.148;
let Json.146 : I32 = 34i64;
let Json.145 : U8 = CallByName Num.123 Json.146;
let Json.143 : List U8 = CallByName List.4 Json.144 Json.145;
ret Json.143;
procedure Json.77 (Json.78, Json.105, #Attr.12):
let Json.76 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.76;
dec #Attr.12;
let Json.141 : I32 = 123i64;
let Json.140 : U8 = CallByName Num.123 Json.141;
let Json.80 : List U8 = CallByName List.4 Json.78 Json.140;
let Json.139 : U64 = CallByName List.6 Json.76;
let Json.116 : {List U8, U64} = Struct {Json.80, Json.139};
let Json.117 : {} = Struct {};
let Json.115 : {List U8, U64} = CallByName List.18 Json.76 Json.116 Json.117;
dec Json.76;
let Json.82 : List U8 = StructAtIndex 0 Json.115;
inc Json.82;
dec Json.115;
let Json.114 : I32 = 125i64;
let Json.113 : U8 = CallByName Num.123 Json.114;
let Json.112 : List U8 = CallByName List.4 Json.82 Json.113;
ret Json.112;
procedure Json.79 (Json.110, Json.111):
let Json.85 : Str = StructAtIndex 0 Json.111;
inc Json.85;
let Json.86 : {Str} = StructAtIndex 1 Json.111;
inc Json.86;
dec Json.111;
let Json.83 : List U8 = StructAtIndex 0 Json.110;
inc Json.83;
let Json.84 : U64 = StructAtIndex 1 Json.110;
dec Json.110;
let Json.138 : I32 = 34i64;
let Json.137 : U8 = CallByName Num.123 Json.138;
let Json.135 : List U8 = CallByName List.4 Json.83 Json.137;
let Json.136 : List U8 = CallByName Str.12 Json.85;
let Json.132 : List U8 = CallByName List.8 Json.135 Json.136;
let Json.134 : I32 = 34i64;
let Json.133 : U8 = CallByName Num.123 Json.134;
let Json.129 : List U8 = CallByName List.4 Json.132 Json.133;
let Json.131 : I32 = 58i64;
let Json.130 : U8 = CallByName Num.123 Json.131;
let Json.127 : List U8 = CallByName List.4 Json.129 Json.130;
let Json.128 : {} = Struct {};
let Json.87 : List U8 = CallByName Encode.23 Json.127 Json.86 Json.128;
joinpoint Json.122 Json.88:
let Json.120 : U64 = 1i64;
let Json.119 : U64 = CallByName Num.20 Json.84 Json.120;
let Json.118 : {List U8, U64} = Struct {Json.88, Json.119};
ret Json.118;
in
let Json.126 : U64 = 0i64;
let Json.123 : Int1 = CallByName Num.24 Json.84 Json.126;
if Json.123 then
let Json.125 : I32 = 44i64;
let Json.124 : U8 = CallByName Num.123 Json.125;
let Json.121 : List U8 = CallByName List.4 Json.87 Json.124;
jump Json.122 Json.121;
else
jump Json.122 Json.87;
procedure List.122 (List.123, List.124, #Attr.12):
let List.121 : {} = StructAtIndex 0 #Attr.12;
let List.351 : {List U8, U64} = CallByName Json.79 List.123 List.124;
let List.350 : [C [], C {List U8, U64}] = TagId(1) List.351;
ret List.350;
procedure List.18 (List.119, List.120, List.121):
let List.327 : {{}} = Struct {List.121};
let List.321 : [C [], C {List U8, U64}] = CallByName List.63 List.119 List.120 List.327;
let List.324 : U8 = 1i64;
let List.325 : U8 = GetTagId List.321;
let List.326 : Int1 = lowlevel Eq List.324 List.325;
if List.326 then
let List.126 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.321;
inc List.126;
dec List.321;
ret List.126;
else
let List.127 : [] = UnionAtIndex (Id 0) (Index 0) List.321;
dec List.321;
let List.323 : {List U8, U64} = CallByName List.64 List.127;
ret List.323;
procedure List.4 (List.90, List.91):
let List.320 : U64 = 1i64;
let List.319 : List U8 = CallByName List.65 List.90 List.320;
let List.318 : List U8 = CallByName List.66 List.319 List.91;
ret List.318;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure List.6 (#Attr.2):
let List.330 : U64 = lowlevel ListLen #Attr.2;
ret List.330;
procedure List.60 (#Attr.2, #Attr.3):
let List.349 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.349;
procedure List.63 (List.283, List.284, List.285):
let List.335 : U64 = 0i64;
let List.336 : U64 = CallByName List.6 List.283;
let List.334 : [C [], C {List U8, U64}] = CallByName List.77 List.283 List.284 List.285 List.335 List.336;
ret List.334;
procedure List.64 (#Attr.2):
let List.333 : {List U8, U64} = lowlevel Unreachable #Attr.2;
ret List.333;
procedure List.65 (#Attr.2, #Attr.3):
let List.332 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.332;
procedure List.66 (#Attr.2, #Attr.3):
let List.331 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.331;
procedure List.77 (List.368, List.369, List.370, List.371, List.372):
joinpoint List.337 List.286 List.287 List.288 List.289 List.290:
let List.339 : Int1 = CallByName Num.22 List.289 List.290;
if List.339 then
let List.348 : {Str, {Str}} = CallByName List.60 List.286 List.289;
let List.340 : [C [], C {List U8, U64}] = CallByName List.122 List.287 List.348 List.288;
let List.345 : U8 = 1i64;
let List.346 : U8 = GetTagId List.340;
let List.347 : Int1 = lowlevel Eq List.345 List.346;
if List.347 then
let List.291 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.340;
inc List.291;
dec List.340;
let List.343 : U64 = 1i64;
let List.342 : U64 = CallByName Num.19 List.289 List.343;
jump List.337 List.286 List.291 List.288 List.342 List.290;
else
let List.292 : [] = UnionAtIndex (Id 0) (Index 0) List.340;
dec List.340;
let List.344 : [C [], C {List U8, U64}] = TagId(0) List.292;
ret List.344;
else
let List.338 : [C [], C {List U8, U64}] = TagId(1) List.287;
ret List.338;
in
jump List.337 List.368 List.369 List.370 List.371 List.372;
procedure List.8 (#Attr.2, #Attr.3):
let List.329 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.329;
procedure Num.123 (#Attr.2):
let Num.264 : U8 = lowlevel NumIntCast #Attr.2;
ret Num.264;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.267 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.267;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.265 : U64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.265;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.268 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.268;
procedure Num.24 (#Attr.2, #Attr.3):
let Num.266 : Int1 = lowlevel NumGt #Attr.2 #Attr.3;
ret Num.266;
procedure Str.12 (#Attr.2):
let Str.210 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.210;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.204 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.204;
procedure Str.9 (Str.69):
let Str.202 : U64 = 0i64;
let Str.203 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.202 Str.203;
let Str.199 : Int1 = StructAtIndex 2 Str.70;
if Str.199 then
let Str.201 : Str = StructAtIndex 1 Str.70;
inc Str.201;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
dec Str.70;
let Str.196 : {U64, U8} = Struct {Str.198, Str.197};
let Str.195 : [C {U64, U8}, C Str] = TagId(0) Str.196;
ret Str.195;
procedure Test.0 ():
let Test.11 : Str = "foo";
let Test.10 : {} = CallByName Json.1;
let Test.8 : List U8 = CallByName Encode.25 Test.11 Test.10;
let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8;
let Test.5 : U8 = 1i64;
let Test.6 : U8 = GetTagId Test.1;
let Test.7 : Int1 = lowlevel Eq Test.5 Test.6;
if Test.7 then
let Test.2 : Str = UnionAtIndex (Id 1) (Index 0) Test.1;
inc Test.2;
dec Test.1;
ret Test.2;
else
dec Test.1;
let Test.4 : Str = "<bad>";
ret Test.4;

View file

@ -0,0 +1,295 @@
procedure #Derived.0 (#Derived.1):
let #Derived_gen.1 : {{Str, Str}} = Struct {#Derived.1};
let #Derived_gen.0 : {{Str, Str}} = CallByName Encode.22 #Derived_gen.1;
ret #Derived_gen.0;
procedure #Derived.2 (#Derived.3, #Derived.4, #Attr.12):
let #Derived.1 : {Str, Str} = StructAtIndex 0 #Attr.12;
inc #Derived.1;
dec #Attr.12;
let #Derived_gen.11 : Str = "a";
let #Derived_gen.13 : Str = StructAtIndex 0 #Derived.1;
inc #Derived_gen.13;
let #Derived_gen.12 : {Str} = CallByName Json.17 #Derived_gen.13;
let #Derived_gen.6 : {Str, {Str}} = Struct {#Derived_gen.11, #Derived_gen.12};
let #Derived_gen.8 : Str = "b";
let #Derived_gen.10 : Str = StructAtIndex 1 #Derived.1;
inc #Derived_gen.10;
dec #Derived.1;
let #Derived_gen.9 : {Str} = CallByName Json.17 #Derived_gen.10;
let #Derived_gen.7 : {Str, {Str}} = Struct {#Derived_gen.8, #Derived_gen.9};
let #Derived_gen.5 : List {Str, {Str}} = Array [#Derived_gen.6, #Derived_gen.7];
let #Derived_gen.4 : {List {Str, {Str}}} = CallByName Json.19 #Derived_gen.5;
let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4;
ret #Derived_gen.3;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.106 : List U8 = CallByName #Derived.2 Encode.94 Encode.96 Encode.102;
ret Encode.106;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.113 : List U8 = CallByName Json.77 Encode.94 Encode.96 Encode.102;
ret Encode.113;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.117 : List U8 = CallByName Json.65 Encode.94 Encode.96 Encode.102;
ret Encode.117;
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {{Str, Str}} = CallByName #Derived.0 Encode.100;
let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101;
ret Encode.103;
procedure Json.1 ():
let Json.102 : {} = Struct {};
ret Json.102;
procedure Json.17 (Json.64):
let Json.110 : {Str} = Struct {Json.64};
let Json.109 : {Str} = CallByName Encode.22 Json.110;
ret Json.109;
procedure Json.19 (Json.76):
let Json.104 : {List {Str, {Str}}} = Struct {Json.76};
let Json.103 : {List {Str, {Str}}} = CallByName Encode.22 Json.104;
ret Json.103;
procedure Json.65 (Json.66, Json.108, #Attr.12):
let Json.64 : Str = StructAtIndex 0 #Attr.12;
inc Json.64;
dec #Attr.12;
let Json.153 : I32 = 34i64;
let Json.152 : U8 = CallByName Num.123 Json.153;
let Json.150 : List U8 = CallByName List.4 Json.66 Json.152;
let Json.151 : List U8 = CallByName Str.12 Json.64;
let Json.147 : List U8 = CallByName List.8 Json.150 Json.151;
let Json.149 : I32 = 34i64;
let Json.148 : U8 = CallByName Num.123 Json.149;
let Json.146 : List U8 = CallByName List.4 Json.147 Json.148;
ret Json.146;
procedure Json.77 (Json.78, Json.105, #Attr.12):
let Json.76 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.76;
dec #Attr.12;
let Json.144 : I32 = 123i64;
let Json.143 : U8 = CallByName Num.123 Json.144;
let Json.80 : List U8 = CallByName List.4 Json.78 Json.143;
let Json.142 : U64 = CallByName List.6 Json.76;
let Json.119 : {List U8, U64} = Struct {Json.80, Json.142};
let Json.120 : {} = Struct {};
let Json.118 : {List U8, U64} = CallByName List.18 Json.76 Json.119 Json.120;
dec Json.76;
let Json.82 : List U8 = StructAtIndex 0 Json.118;
inc Json.82;
dec Json.118;
let Json.117 : I32 = 125i64;
let Json.116 : U8 = CallByName Num.123 Json.117;
let Json.115 : List U8 = CallByName List.4 Json.82 Json.116;
ret Json.115;
procedure Json.79 (Json.113, Json.114):
let Json.85 : Str = StructAtIndex 0 Json.114;
inc Json.85;
let Json.86 : {Str} = StructAtIndex 1 Json.114;
inc Json.86;
dec Json.114;
let Json.83 : List U8 = StructAtIndex 0 Json.113;
inc Json.83;
let Json.84 : U64 = StructAtIndex 1 Json.113;
dec Json.113;
let Json.141 : I32 = 34i64;
let Json.140 : U8 = CallByName Num.123 Json.141;
let Json.138 : List U8 = CallByName List.4 Json.83 Json.140;
let Json.139 : List U8 = CallByName Str.12 Json.85;
let Json.135 : List U8 = CallByName List.8 Json.138 Json.139;
let Json.137 : I32 = 34i64;
let Json.136 : U8 = CallByName Num.123 Json.137;
let Json.132 : List U8 = CallByName List.4 Json.135 Json.136;
let Json.134 : I32 = 58i64;
let Json.133 : U8 = CallByName Num.123 Json.134;
let Json.130 : List U8 = CallByName List.4 Json.132 Json.133;
let Json.131 : {} = Struct {};
let Json.87 : List U8 = CallByName Encode.23 Json.130 Json.86 Json.131;
joinpoint Json.125 Json.88:
let Json.123 : U64 = 1i64;
let Json.122 : U64 = CallByName Num.20 Json.84 Json.123;
let Json.121 : {List U8, U64} = Struct {Json.88, Json.122};
ret Json.121;
in
let Json.129 : U64 = 0i64;
let Json.126 : Int1 = CallByName Num.24 Json.84 Json.129;
if Json.126 then
let Json.128 : I32 = 44i64;
let Json.127 : U8 = CallByName Num.123 Json.128;
let Json.124 : List U8 = CallByName List.4 Json.87 Json.127;
jump Json.125 Json.124;
else
jump Json.125 Json.87;
procedure List.122 (List.123, List.124, #Attr.12):
let List.121 : {} = StructAtIndex 0 #Attr.12;
let List.351 : {List U8, U64} = CallByName Json.79 List.123 List.124;
let List.350 : [C [], C {List U8, U64}] = TagId(1) List.351;
ret List.350;
procedure List.18 (List.119, List.120, List.121):
let List.327 : {{}} = Struct {List.121};
let List.321 : [C [], C {List U8, U64}] = CallByName List.63 List.119 List.120 List.327;
let List.324 : U8 = 1i64;
let List.325 : U8 = GetTagId List.321;
let List.326 : Int1 = lowlevel Eq List.324 List.325;
if List.326 then
let List.126 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.321;
inc List.126;
dec List.321;
ret List.126;
else
let List.127 : [] = UnionAtIndex (Id 0) (Index 0) List.321;
dec List.321;
let List.323 : {List U8, U64} = CallByName List.64 List.127;
ret List.323;
procedure List.4 (List.90, List.91):
let List.320 : U64 = 1i64;
let List.319 : List U8 = CallByName List.65 List.90 List.320;
let List.318 : List U8 = CallByName List.66 List.319 List.91;
ret List.318;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure List.6 (#Attr.2):
let List.330 : U64 = lowlevel ListLen #Attr.2;
ret List.330;
procedure List.60 (#Attr.2, #Attr.3):
let List.349 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.349;
procedure List.63 (List.283, List.284, List.285):
let List.335 : U64 = 0i64;
let List.336 : U64 = CallByName List.6 List.283;
let List.334 : [C [], C {List U8, U64}] = CallByName List.77 List.283 List.284 List.285 List.335 List.336;
ret List.334;
procedure List.64 (#Attr.2):
let List.333 : {List U8, U64} = lowlevel Unreachable #Attr.2;
ret List.333;
procedure List.65 (#Attr.2, #Attr.3):
let List.332 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.332;
procedure List.66 (#Attr.2, #Attr.3):
let List.331 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.331;
procedure List.77 (List.368, List.369, List.370, List.371, List.372):
joinpoint List.337 List.286 List.287 List.288 List.289 List.290:
let List.339 : Int1 = CallByName Num.22 List.289 List.290;
if List.339 then
let List.348 : {Str, {Str}} = CallByName List.60 List.286 List.289;
let List.340 : [C [], C {List U8, U64}] = CallByName List.122 List.287 List.348 List.288;
let List.345 : U8 = 1i64;
let List.346 : U8 = GetTagId List.340;
let List.347 : Int1 = lowlevel Eq List.345 List.346;
if List.347 then
let List.291 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.340;
inc List.291;
dec List.340;
let List.343 : U64 = 1i64;
let List.342 : U64 = CallByName Num.19 List.289 List.343;
jump List.337 List.286 List.291 List.288 List.342 List.290;
else
let List.292 : [] = UnionAtIndex (Id 0) (Index 0) List.340;
dec List.340;
let List.344 : [C [], C {List U8, U64}] = TagId(0) List.292;
ret List.344;
else
let List.338 : [C [], C {List U8, U64}] = TagId(1) List.287;
ret List.338;
in
jump List.337 List.368 List.369 List.370 List.371 List.372;
procedure List.8 (#Attr.2, #Attr.3):
let List.329 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.329;
procedure Num.123 (#Attr.2):
let Num.264 : U8 = lowlevel NumIntCast #Attr.2;
ret Num.264;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.267 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.267;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.265 : U64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.265;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.268 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.268;
procedure Num.24 (#Attr.2, #Attr.3):
let Num.266 : Int1 = lowlevel NumGt #Attr.2 #Attr.3;
ret Num.266;
procedure Str.12 (#Attr.2):
let Str.210 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.210;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.204 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.204;
procedure Str.9 (Str.69):
let Str.202 : U64 = 0i64;
let Str.203 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.202 Str.203;
let Str.199 : Int1 = StructAtIndex 2 Str.70;
if Str.199 then
let Str.201 : Str = StructAtIndex 1 Str.70;
inc Str.201;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
dec Str.70;
let Str.196 : {U64, U8} = Struct {Str.198, Str.197};
let Str.195 : [C {U64, U8}, C Str] = TagId(0) Str.196;
ret Str.195;
procedure Test.0 ():
let Test.11 : Str = "foo";
let Test.12 : Str = "bar";
let Test.9 : {Str, Str} = Struct {Test.11, Test.12};
let Test.10 : {} = CallByName Json.1;
let Test.8 : List U8 = CallByName Encode.25 Test.9 Test.10;
let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8;
let Test.5 : U8 = 1i64;
let Test.6 : U8 = GetTagId Test.1;
let Test.7 : Int1 = lowlevel Eq Test.5 Test.6;
if Test.7 then
let Test.2 : Str = UnionAtIndex (Id 1) (Index 0) Test.1;
inc Test.2;
dec Test.1;
ret Test.2;
else
dec Test.1;
let Test.4 : Str = "<bad>";
ret Test.4;

View file

@ -0,0 +1,106 @@
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.106 : List U8 = CallByName Json.65 Encode.94 Encode.96 Encode.102;
ret Encode.106;
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {Str} = CallByName Json.17 Encode.100;
let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101;
ret Encode.103;
procedure Json.1 ():
let Json.102 : {} = Struct {};
ret Json.102;
procedure Json.17 (Json.64):
let Json.104 : {Str} = Struct {Json.64};
let Json.103 : {Str} = CallByName Encode.22 Json.104;
ret Json.103;
procedure Json.65 (Json.66, Json.105, #Attr.12):
let Json.64 : Str = StructAtIndex 0 #Attr.12;
inc Json.64;
dec #Attr.12;
let Json.114 : I32 = 34i64;
let Json.113 : U8 = CallByName Num.123 Json.114;
let Json.111 : List U8 = CallByName List.4 Json.66 Json.113;
let Json.112 : List U8 = CallByName Str.12 Json.64;
let Json.108 : List U8 = CallByName List.8 Json.111 Json.112;
let Json.110 : I32 = 34i64;
let Json.109 : U8 = CallByName Num.123 Json.110;
let Json.107 : List U8 = CallByName List.4 Json.108 Json.109;
ret Json.107;
procedure List.4 (List.90, List.91):
let List.302 : U64 = 1i64;
let List.301 : List U8 = CallByName List.65 List.90 List.302;
let List.300 : List U8 = CallByName List.66 List.301 List.91;
ret List.300;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure List.65 (#Attr.2, #Attr.3):
let List.305 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.305;
procedure List.66 (#Attr.2, #Attr.3):
let List.304 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.304;
procedure List.8 (#Attr.2, #Attr.3):
let List.303 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.303;
procedure Num.123 (#Attr.2):
let Num.258 : U8 = lowlevel NumIntCast #Attr.2;
ret Num.258;
procedure Str.12 (#Attr.2):
let Str.209 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.209;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.204 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.204;
procedure Str.9 (Str.69):
let Str.202 : U64 = 0i64;
let Str.203 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.202 Str.203;
let Str.199 : Int1 = StructAtIndex 2 Str.70;
if Str.199 then
let Str.201 : Str = StructAtIndex 1 Str.70;
inc Str.201;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
dec Str.70;
let Str.196 : {U64, U8} = Struct {Str.198, Str.197};
let Str.195 : [C {U64, U8}, C Str] = TagId(0) Str.196;
ret Str.195;
procedure Test.0 ():
let Test.9 : Str = "abc";
let Test.10 : {} = CallByName Json.1;
let Test.8 : List U8 = CallByName Encode.25 Test.9 Test.10;
let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8;
let Test.5 : U8 = 1i64;
let Test.6 : U8 = GetTagId Test.1;
let Test.7 : Int1 = lowlevel Eq Test.5 Test.6;
if Test.7 then
let Test.2 : Str = UnionAtIndex (Id 1) (Index 0) Test.1;
inc Test.2;
dec Test.1;
ret Test.2;
else
dec Test.1;
let Test.4 : Str = "<bad>";
ret Test.4;

View file

@ -0,0 +1,290 @@
procedure #Derived.0 (#Derived.1):
let #Derived_gen.1 : {Str} = Struct {#Derived.1};
let #Derived_gen.0 : {Str} = CallByName Encode.22 #Derived_gen.1;
ret #Derived_gen.0;
procedure #Derived.3 (#Derived.4, #Derived.5, #Attr.12):
let #Derived.1 : Str = StructAtIndex 0 #Attr.12;
inc #Derived.1;
dec #Attr.12;
joinpoint #Derived_gen.5 #Derived_gen.4:
let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.4 #Derived_gen.4 #Derived.5;
ret #Derived_gen.3;
in
let #Derived_gen.7 : Str = "A";
let #Derived_gen.9 : {Str} = CallByName Json.17 #Derived.1;
let #Derived_gen.8 : List {Str} = Array [#Derived_gen.9];
let #Derived_gen.6 : {Str, List {Str}} = CallByName Json.20 #Derived_gen.7 #Derived_gen.8;
jump #Derived_gen.5 #Derived_gen.6;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.106 : List U8 = CallByName #Derived.3 Encode.94 Encode.96 Encode.102;
ret Encode.106;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.113 : List U8 = CallByName Json.91 Encode.94 Encode.96 Encode.102;
ret Encode.113;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.116 : List U8 = CallByName Json.65 Encode.94 Encode.96 Encode.102;
ret Encode.116;
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {Str} = CallByName #Derived.0 Encode.100;
let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101;
ret Encode.103;
procedure Json.1 ():
let Json.102 : {} = Struct {};
ret Json.102;
procedure Json.17 (Json.64):
let Json.107 : {Str} = Struct {Json.64};
let Json.106 : {Str} = CallByName Encode.22 Json.107;
ret Json.106;
procedure Json.20 (Json.89, Json.90):
let Json.104 : {Str, List {Str}} = Struct {Json.89, Json.90};
let Json.103 : {Str, List {Str}} = CallByName Encode.22 Json.104;
ret Json.103;
procedure Json.65 (Json.66, Json.108, #Attr.12):
let Json.64 : Str = StructAtIndex 0 #Attr.12;
inc Json.64;
dec #Attr.12;
let Json.155 : I32 = 34i64;
let Json.154 : U8 = CallByName Num.123 Json.155;
let Json.152 : List U8 = CallByName List.4 Json.66 Json.154;
let Json.153 : List U8 = CallByName Str.12 Json.64;
let Json.149 : List U8 = CallByName List.8 Json.152 Json.153;
let Json.151 : I32 = 34i64;
let Json.150 : U8 = CallByName Num.123 Json.151;
let Json.148 : List U8 = CallByName List.4 Json.149 Json.150;
ret Json.148;
procedure Json.91 (Json.92, Json.105, #Attr.12):
let Json.90 : List {Str} = StructAtIndex 1 #Attr.12;
inc Json.90;
let Json.89 : Str = StructAtIndex 0 #Attr.12;
inc Json.89;
dec #Attr.12;
let Json.146 : I32 = 123i64;
let Json.145 : U8 = CallByName Num.123 Json.146;
let Json.142 : List U8 = CallByName List.4 Json.92 Json.145;
let Json.144 : I32 = 34i64;
let Json.143 : U8 = CallByName Num.123 Json.144;
let Json.140 : List U8 = CallByName List.4 Json.142 Json.143;
let Json.141 : List U8 = CallByName Str.12 Json.89;
let Json.137 : List U8 = CallByName List.8 Json.140 Json.141;
let Json.139 : I32 = 34i64;
let Json.138 : U8 = CallByName Num.123 Json.139;
let Json.134 : List U8 = CallByName List.4 Json.137 Json.138;
let Json.136 : I32 = 58i64;
let Json.135 : U8 = CallByName Num.123 Json.136;
let Json.131 : List U8 = CallByName List.4 Json.134 Json.135;
let Json.133 : I32 = 91i64;
let Json.132 : U8 = CallByName Num.123 Json.133;
let Json.94 : List U8 = CallByName List.4 Json.131 Json.132;
let Json.130 : U64 = CallByName List.6 Json.90;
let Json.118 : {List U8, U64} = Struct {Json.94, Json.130};
let Json.119 : {} = Struct {};
let Json.117 : {List U8, U64} = CallByName List.18 Json.90 Json.118 Json.119;
dec Json.90;
let Json.96 : List U8 = StructAtIndex 0 Json.117;
inc Json.96;
dec Json.117;
let Json.116 : I32 = 93i64;
let Json.115 : U8 = CallByName Num.123 Json.116;
let Json.112 : List U8 = CallByName List.4 Json.96 Json.115;
let Json.114 : I32 = 125i64;
let Json.113 : U8 = CallByName Num.123 Json.114;
let Json.111 : List U8 = CallByName List.4 Json.112 Json.113;
ret Json.111;
procedure Json.93 (Json.110, Json.99):
let Json.97 : List U8 = StructAtIndex 0 Json.110;
inc Json.97;
let Json.98 : U64 = StructAtIndex 1 Json.110;
dec Json.110;
let Json.129 : {} = Struct {};
let Json.100 : List U8 = CallByName Encode.23 Json.97 Json.99 Json.129;
joinpoint Json.124 Json.101:
let Json.122 : U64 = 1i64;
let Json.121 : U64 = CallByName Num.20 Json.98 Json.122;
let Json.120 : {List U8, U64} = Struct {Json.101, Json.121};
ret Json.120;
in
let Json.128 : U64 = 0i64;
let Json.125 : Int1 = CallByName Num.24 Json.98 Json.128;
if Json.125 then
let Json.127 : I32 = 44i64;
let Json.126 : U8 = CallByName Num.123 Json.127;
let Json.123 : List U8 = CallByName List.4 Json.100 Json.126;
jump Json.124 Json.123;
else
jump Json.124 Json.100;
procedure List.122 (List.123, List.124, #Attr.12):
let List.121 : {} = StructAtIndex 0 #Attr.12;
let List.357 : {List U8, U64} = CallByName Json.93 List.123 List.124;
let List.356 : [C [], C {List U8, U64}] = TagId(1) List.357;
ret List.356;
procedure List.18 (List.119, List.120, List.121):
let List.333 : {{}} = Struct {List.121};
let List.327 : [C [], C {List U8, U64}] = CallByName List.63 List.119 List.120 List.333;
let List.330 : U8 = 1i64;
let List.331 : U8 = GetTagId List.327;
let List.332 : Int1 = lowlevel Eq List.330 List.331;
if List.332 then
let List.126 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.327;
inc List.126;
dec List.327;
ret List.126;
else
let List.127 : [] = UnionAtIndex (Id 0) (Index 0) List.327;
dec List.327;
let List.329 : {List U8, U64} = CallByName List.64 List.127;
ret List.329;
procedure List.4 (List.90, List.91):
let List.326 : U64 = 1i64;
let List.325 : List U8 = CallByName List.65 List.90 List.326;
let List.324 : List U8 = CallByName List.66 List.325 List.91;
ret List.324;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure List.6 (#Attr.2):
let List.334 : U64 = lowlevel ListLen #Attr.2;
ret List.334;
procedure List.60 (#Attr.2, #Attr.3):
let List.355 : {Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.355;
procedure List.63 (List.283, List.284, List.285):
let List.341 : U64 = 0i64;
let List.342 : U64 = CallByName List.6 List.283;
let List.340 : [C [], C {List U8, U64}] = CallByName List.77 List.283 List.284 List.285 List.341 List.342;
ret List.340;
procedure List.64 (#Attr.2):
let List.339 : {List U8, U64} = lowlevel Unreachable #Attr.2;
ret List.339;
procedure List.65 (#Attr.2, #Attr.3):
let List.338 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.338;
procedure List.66 (#Attr.2, #Attr.3):
let List.337 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.337;
procedure List.77 (List.374, List.375, List.376, List.377, List.378):
joinpoint List.343 List.286 List.287 List.288 List.289 List.290:
let List.345 : Int1 = CallByName Num.22 List.289 List.290;
if List.345 then
let List.354 : {Str} = CallByName List.60 List.286 List.289;
let List.346 : [C [], C {List U8, U64}] = CallByName List.122 List.287 List.354 List.288;
let List.351 : U8 = 1i64;
let List.352 : U8 = GetTagId List.346;
let List.353 : Int1 = lowlevel Eq List.351 List.352;
if List.353 then
let List.291 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.346;
inc List.291;
dec List.346;
let List.349 : U64 = 1i64;
let List.348 : U64 = CallByName Num.19 List.289 List.349;
jump List.343 List.286 List.291 List.288 List.348 List.290;
else
let List.292 : [] = UnionAtIndex (Id 0) (Index 0) List.346;
dec List.346;
let List.350 : [C [], C {List U8, U64}] = TagId(0) List.292;
ret List.350;
else
let List.344 : [C [], C {List U8, U64}] = TagId(1) List.287;
ret List.344;
in
jump List.343 List.374 List.375 List.376 List.377 List.378;
procedure List.8 (#Attr.2, #Attr.3):
let List.336 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.336;
procedure Num.123 (#Attr.2):
let Num.266 : U8 = lowlevel NumIntCast #Attr.2;
ret Num.266;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.269 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.269;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.267 : U64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.267;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.270 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.270;
procedure Num.24 (#Attr.2, #Attr.3):
let Num.268 : Int1 = lowlevel NumGt #Attr.2 #Attr.3;
ret Num.268;
procedure Str.12 (#Attr.2):
let Str.210 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.210;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.204 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.204;
procedure Str.9 (Str.69):
let Str.202 : U64 = 0i64;
let Str.203 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.202 Str.203;
let Str.199 : Int1 = StructAtIndex 2 Str.70;
if Str.199 then
let Str.201 : Str = StructAtIndex 1 Str.70;
inc Str.201;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
dec Str.70;
let Str.196 : {U64, U8} = Struct {Str.198, Str.197};
let Str.195 : [C {U64, U8}, C Str] = TagId(0) Str.196;
ret Str.195;
procedure Test.0 ():
let Test.12 : Str = "foo";
let Test.11 : {} = CallByName Json.1;
let Test.10 : List U8 = CallByName Encode.25 Test.12 Test.11;
let Test.2 : [C {U64, U8}, C Str] = CallByName Str.9 Test.10;
let Test.7 : U8 = 1i64;
let Test.8 : U8 = GetTagId Test.2;
let Test.9 : Int1 = lowlevel Eq Test.7 Test.8;
if Test.9 then
let Test.4 : Str = UnionAtIndex (Id 1) (Index 0) Test.2;
inc Test.4;
dec Test.2;
ret Test.4;
else
dec Test.2;
let Test.6 : Str = "<bad>";
ret Test.6;

View file

@ -0,0 +1,298 @@
procedure #Derived.0 (#Derived.1):
let #Derived_gen.1 : {{Str, Str}} = Struct {#Derived.1};
let #Derived_gen.0 : {{Str, Str}} = CallByName Encode.22 #Derived_gen.1;
ret #Derived_gen.0;
procedure #Derived.4 (#Derived.5, #Derived.6, #Attr.12):
let #Derived.1 : {Str, Str} = StructAtIndex 0 #Attr.12;
inc #Derived.1;
dec #Attr.12;
joinpoint #Derived_gen.5 #Derived_gen.4:
let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.5 #Derived_gen.4 #Derived.6;
ret #Derived_gen.3;
in
let #Derived.2 : Str = StructAtIndex 0 #Derived.1;
inc #Derived.2;
let #Derived.3 : Str = StructAtIndex 1 #Derived.1;
inc #Derived.3;
dec #Derived.1;
let #Derived_gen.7 : Str = "A";
let #Derived_gen.9 : {Str} = CallByName Json.17 #Derived.2;
let #Derived_gen.10 : {Str} = CallByName Json.17 #Derived.3;
let #Derived_gen.8 : List {Str} = Array [#Derived_gen.9, #Derived_gen.10];
let #Derived_gen.6 : {Str, List {Str}} = CallByName Json.20 #Derived_gen.7 #Derived_gen.8;
jump #Derived_gen.5 #Derived_gen.6;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.22 (Encode.93):
ret Encode.93;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.106 : List U8 = CallByName #Derived.4 Encode.94 Encode.96 Encode.102;
ret Encode.106;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.113 : List U8 = CallByName Json.91 Encode.94 Encode.96 Encode.102;
ret Encode.113;
procedure Encode.23 (Encode.94, Encode.102, Encode.96):
let Encode.117 : List U8 = CallByName Json.65 Encode.94 Encode.96 Encode.102;
ret Encode.117;
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {{Str, Str}} = CallByName #Derived.0 Encode.100;
let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101;
ret Encode.103;
procedure Json.1 ():
let Json.102 : {} = Struct {};
ret Json.102;
procedure Json.17 (Json.64):
let Json.110 : {Str} = Struct {Json.64};
let Json.109 : {Str} = CallByName Encode.22 Json.110;
ret Json.109;
procedure Json.20 (Json.89, Json.90):
let Json.104 : {Str, List {Str}} = Struct {Json.89, Json.90};
let Json.103 : {Str, List {Str}} = CallByName Encode.22 Json.104;
ret Json.103;
procedure Json.65 (Json.66, Json.108, #Attr.12):
let Json.64 : Str = StructAtIndex 0 #Attr.12;
inc Json.64;
dec #Attr.12;
let Json.158 : I32 = 34i64;
let Json.157 : U8 = CallByName Num.123 Json.158;
let Json.155 : List U8 = CallByName List.4 Json.66 Json.157;
let Json.156 : List U8 = CallByName Str.12 Json.64;
let Json.152 : List U8 = CallByName List.8 Json.155 Json.156;
let Json.154 : I32 = 34i64;
let Json.153 : U8 = CallByName Num.123 Json.154;
let Json.151 : List U8 = CallByName List.4 Json.152 Json.153;
ret Json.151;
procedure Json.91 (Json.92, Json.105, #Attr.12):
let Json.90 : List {Str} = StructAtIndex 1 #Attr.12;
inc Json.90;
let Json.89 : Str = StructAtIndex 0 #Attr.12;
inc Json.89;
dec #Attr.12;
let Json.149 : I32 = 123i64;
let Json.148 : U8 = CallByName Num.123 Json.149;
let Json.145 : List U8 = CallByName List.4 Json.92 Json.148;
let Json.147 : I32 = 34i64;
let Json.146 : U8 = CallByName Num.123 Json.147;
let Json.143 : List U8 = CallByName List.4 Json.145 Json.146;
let Json.144 : List U8 = CallByName Str.12 Json.89;
let Json.140 : List U8 = CallByName List.8 Json.143 Json.144;
let Json.142 : I32 = 34i64;
let Json.141 : U8 = CallByName Num.123 Json.142;
let Json.137 : List U8 = CallByName List.4 Json.140 Json.141;
let Json.139 : I32 = 58i64;
let Json.138 : U8 = CallByName Num.123 Json.139;
let Json.134 : List U8 = CallByName List.4 Json.137 Json.138;
let Json.136 : I32 = 91i64;
let Json.135 : U8 = CallByName Num.123 Json.136;
let Json.94 : List U8 = CallByName List.4 Json.134 Json.135;
let Json.133 : U64 = CallByName List.6 Json.90;
let Json.121 : {List U8, U64} = Struct {Json.94, Json.133};
let Json.122 : {} = Struct {};
let Json.120 : {List U8, U64} = CallByName List.18 Json.90 Json.121 Json.122;
dec Json.90;
let Json.96 : List U8 = StructAtIndex 0 Json.120;
inc Json.96;
dec Json.120;
let Json.119 : I32 = 93i64;
let Json.118 : U8 = CallByName Num.123 Json.119;
let Json.115 : List U8 = CallByName List.4 Json.96 Json.118;
let Json.117 : I32 = 125i64;
let Json.116 : U8 = CallByName Num.123 Json.117;
let Json.114 : List U8 = CallByName List.4 Json.115 Json.116;
ret Json.114;
procedure Json.93 (Json.113, Json.99):
let Json.97 : List U8 = StructAtIndex 0 Json.113;
inc Json.97;
let Json.98 : U64 = StructAtIndex 1 Json.113;
dec Json.113;
let Json.132 : {} = Struct {};
let Json.100 : List U8 = CallByName Encode.23 Json.97 Json.99 Json.132;
joinpoint Json.127 Json.101:
let Json.125 : U64 = 1i64;
let Json.124 : U64 = CallByName Num.20 Json.98 Json.125;
let Json.123 : {List U8, U64} = Struct {Json.101, Json.124};
ret Json.123;
in
let Json.131 : U64 = 0i64;
let Json.128 : Int1 = CallByName Num.24 Json.98 Json.131;
if Json.128 then
let Json.130 : I32 = 44i64;
let Json.129 : U8 = CallByName Num.123 Json.130;
let Json.126 : List U8 = CallByName List.4 Json.100 Json.129;
jump Json.127 Json.126;
else
jump Json.127 Json.100;
procedure List.122 (List.123, List.124, #Attr.12):
let List.121 : {} = StructAtIndex 0 #Attr.12;
let List.357 : {List U8, U64} = CallByName Json.93 List.123 List.124;
let List.356 : [C [], C {List U8, U64}] = TagId(1) List.357;
ret List.356;
procedure List.18 (List.119, List.120, List.121):
let List.333 : {{}} = Struct {List.121};
let List.327 : [C [], C {List U8, U64}] = CallByName List.63 List.119 List.120 List.333;
let List.330 : U8 = 1i64;
let List.331 : U8 = GetTagId List.327;
let List.332 : Int1 = lowlevel Eq List.330 List.331;
if List.332 then
let List.126 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.327;
inc List.126;
dec List.327;
ret List.126;
else
let List.127 : [] = UnionAtIndex (Id 0) (Index 0) List.327;
dec List.327;
let List.329 : {List U8, U64} = CallByName List.64 List.127;
ret List.329;
procedure List.4 (List.90, List.91):
let List.326 : U64 = 1i64;
let List.325 : List U8 = CallByName List.65 List.90 List.326;
let List.324 : List U8 = CallByName List.66 List.325 List.91;
ret List.324;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure List.6 (#Attr.2):
let List.334 : U64 = lowlevel ListLen #Attr.2;
ret List.334;
procedure List.60 (#Attr.2, #Attr.3):
let List.355 : {Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.355;
procedure List.63 (List.283, List.284, List.285):
let List.341 : U64 = 0i64;
let List.342 : U64 = CallByName List.6 List.283;
let List.340 : [C [], C {List U8, U64}] = CallByName List.77 List.283 List.284 List.285 List.341 List.342;
ret List.340;
procedure List.64 (#Attr.2):
let List.339 : {List U8, U64} = lowlevel Unreachable #Attr.2;
ret List.339;
procedure List.65 (#Attr.2, #Attr.3):
let List.338 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.338;
procedure List.66 (#Attr.2, #Attr.3):
let List.337 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.337;
procedure List.77 (List.374, List.375, List.376, List.377, List.378):
joinpoint List.343 List.286 List.287 List.288 List.289 List.290:
let List.345 : Int1 = CallByName Num.22 List.289 List.290;
if List.345 then
let List.354 : {Str} = CallByName List.60 List.286 List.289;
let List.346 : [C [], C {List U8, U64}] = CallByName List.122 List.287 List.354 List.288;
let List.351 : U8 = 1i64;
let List.352 : U8 = GetTagId List.346;
let List.353 : Int1 = lowlevel Eq List.351 List.352;
if List.353 then
let List.291 : {List U8, U64} = UnionAtIndex (Id 1) (Index 0) List.346;
inc List.291;
dec List.346;
let List.349 : U64 = 1i64;
let List.348 : U64 = CallByName Num.19 List.289 List.349;
jump List.343 List.286 List.291 List.288 List.348 List.290;
else
let List.292 : [] = UnionAtIndex (Id 0) (Index 0) List.346;
dec List.346;
let List.350 : [C [], C {List U8, U64}] = TagId(0) List.292;
ret List.350;
else
let List.344 : [C [], C {List U8, U64}] = TagId(1) List.287;
ret List.344;
in
jump List.343 List.374 List.375 List.376 List.377 List.378;
procedure List.8 (#Attr.2, #Attr.3):
let List.336 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.336;
procedure Num.123 (#Attr.2):
let Num.266 : U8 = lowlevel NumIntCast #Attr.2;
ret Num.266;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.269 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.269;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.267 : U64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.267;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.270 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.270;
procedure Num.24 (#Attr.2, #Attr.3):
let Num.268 : Int1 = lowlevel NumGt #Attr.2 #Attr.3;
ret Num.268;
procedure Str.12 (#Attr.2):
let Str.210 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.210;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.204 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.204;
procedure Str.9 (Str.69):
let Str.202 : U64 = 0i64;
let Str.203 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.202 Str.203;
let Str.199 : Int1 = StructAtIndex 2 Str.70;
if Str.199 then
let Str.201 : Str = StructAtIndex 1 Str.70;
inc Str.201;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
dec Str.70;
let Str.196 : {U64, U8} = Struct {Str.198, Str.197};
let Str.195 : [C {U64, U8}, C Str] = TagId(0) Str.196;
ret Str.195;
procedure Test.0 ():
let Test.13 : Str = "foo";
let Test.12 : Str = "foo";
let Test.1 : {Str, Str} = Struct {Test.12, Test.13};
let Test.11 : {} = CallByName Json.1;
let Test.10 : List U8 = CallByName Encode.25 Test.1 Test.11;
let Test.2 : [C {U64, U8}, C Str] = CallByName Str.9 Test.10;
let Test.7 : U8 = 1i64;
let Test.8 : U8 = GetTagId Test.2;
let Test.9 : Int1 = lowlevel Eq Test.7 Test.8;
if Test.9 then
let Test.4 : Str = UnionAtIndex (Id 1) (Index 0) Test.2;
inc Test.4;
dec Test.2;
ret Test.4;
else
dec Test.2;
let Test.6 : Str = "<bad>";
ret Test.6;

View file

@ -1,7 +1,7 @@
procedure List.28 (#Attr.2, #Attr.3):
let List.297 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3;
let Bool.9 : Int1 = lowlevel ListIsUnique #Attr.2;
if Bool.9 then
let #Derived_gen.0 : Int1 = lowlevel ListIsUnique #Attr.2;
if #Derived_gen.0 then
ret List.297;
else
decref #Attr.2;

View file

@ -1467,7 +1467,24 @@ fn encode_custom_type() {
}
#[mono_test]
#[ignore]
fn encode_derived_string() {
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
result = Str.fromUtf8 (Encode.toBytes "abc" Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
)
}
#[mono_test]
#[ignore = "TODO"]
fn encode_derived_record() {
indoc!(
r#"
@ -1476,7 +1493,7 @@ fn encode_derived_record() {
provides [main] to "./platform"
main =
result = Str.fromUtf8 (Encode.toBytes {a: "fieldA", b: "fieldB"} Json.format)
result = Str.fromUtf8 (Encode.toBytes {a: "a"} Json.format)
when result is
Ok s -> s
_ -> "<bad>"
@ -1780,3 +1797,92 @@ fn instantiate_annotated_as_recursive_alias_multiple_polymorphic_expr() {
"#
)
}
#[mono_test]
fn encode_derived_record_one_field_string() {
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
result = Str.fromUtf8 (Encode.toBytes {a: "foo"} Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
)
}
#[mono_test]
fn encode_derived_record_two_field_strings() {
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
result = Str.fromUtf8 (Encode.toBytes {a: "foo", b: "bar"} Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
)
}
#[mono_test]
fn encode_derived_nested_record_string() {
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
result = Str.fromUtf8 (Encode.toBytes {a: {b: "bar"}} Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
)
}
#[mono_test]
fn encode_derived_tag_one_field_string() {
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
x : [A Str]
x = A "foo"
result = Str.fromUtf8 (Encode.toBytes x Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
)
}
#[mono_test]
fn encode_derived_tag_two_payloads_string() {
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
x : [A Str Str]
x = A "foo" "foo"
result = Str.fromUtf8 (Encode.toBytes x Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
)
}

View file

@ -706,6 +706,12 @@ impl GetSubsSlice<Variable> for Subs {
}
}
impl GetSubsSlice<VariableSubsSlice> for Subs {
fn get_subs_slice(&self, subs_slice: SubsSlice<VariableSubsSlice>) -> &[VariableSubsSlice] {
subs_slice.get_slice(&self.variable_slices)
}
}
impl GetSubsSlice<RecordField<()>> for Subs {
fn get_subs_slice(&self, subs_slice: SubsSlice<RecordField<()>>) -> &[RecordField<()>] {
subs_slice.get_slice(&self.record_fields)
@ -3953,12 +3959,15 @@ fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase {
/// - all implicitly exposed variables, which include
/// - ability member specializations
/// - specialization lambda sets under specialization ability members
/// - lambda sets under ability members defined in the module
#[derive(Clone, Debug)]
pub struct ExposedTypesStorageSubs {
pub storage_subs: StorageSubs,
pub stored_vars_by_symbol: VecMap<Symbol, Variable>,
/// lambda set var in other module -> var in storage subs
/// specialization lambda set var in other module -> var in storage subs
pub stored_specialization_lambda_set_vars: VecMap<Variable, Variable>,
/// ability member signature in other module -> var in storage subs
pub stored_ability_member_vars: VecMap<Variable, Variable>,
}
#[derive(Clone, Debug)]
@ -4666,6 +4675,7 @@ pub struct CopiedImport {
struct CopyImportEnv<'a> {
visited: bumpalo::collections::Vec<'a, Variable>,
/// source variable -> target variable
copy_table: &'a mut VecMap<Variable, Variable>,
source: &'a Subs,
target: &'a mut Subs,
@ -5281,3 +5291,81 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
});
}
}
/// Finds the lambda set of the ability member type (not specialization) at the region `r`,
/// or all lambda sets if no region is specified.
///
/// Panics if the given function type does not correspond with what's expected of an ability
/// member, namely its lambda sets have more than a single unspecialized lambda set.
pub fn get_member_lambda_sets_at_region(subs: &Subs, var: Variable, target_region: u8) -> Variable {
let mut stack = vec![var];
while let Some(var) = stack.pop() {
match subs.get_content_without_compacting(var) {
Content::LambdaSet(LambdaSet {
solved,
recursion_var,
unspecialized,
ambient_function: _,
}) => {
debug_assert!(solved.is_empty());
debug_assert!(recursion_var.is_none());
debug_assert_eq!(unspecialized.len(), 1);
let Uls(_, _, region) = subs.get_subs_slice(*unspecialized)[0];
if region == target_region {
return var;
}
}
Content::Structure(flat_type) => match flat_type {
FlatType::Apply(_, vars) => {
stack.extend(subs.get_subs_slice(*vars));
}
FlatType::Func(args, lset, ret) => {
stack.extend(subs.get_subs_slice(*args));
stack.push(*lset);
stack.push(*ret);
}
FlatType::Record(fields, ext) => {
stack.extend(subs.get_subs_slice(fields.variables()));
stack.push(*ext);
}
FlatType::TagUnion(tags, ext) => {
stack.extend(
subs.get_subs_slice(tags.variables())
.iter()
.flat_map(|slice| subs.get_subs_slice(*slice)),
);
stack.push(*ext);
}
FlatType::FunctionOrTagUnion(_, _, ext) => {
stack.push(*ext);
}
FlatType::RecursiveTagUnion(rec, tags, ext) => {
stack.push(*rec);
stack.extend(
subs.get_subs_slice(tags.variables())
.iter()
.flat_map(|slice| subs.get_subs_slice(*slice)),
);
stack.push(*ext);
}
FlatType::Erroneous(_) | FlatType::EmptyRecord | FlatType::EmptyTagUnion => {}
},
Content::Alias(_, _, real_var, _) => {
stack.push(*real_var);
}
Content::RangedNumber(_)
| Content::Error
| Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _)
| Content::RecursionVar {
structure: _,
opt_name: _,
} => {}
}
}
internal_error!("No lambda set at region {} found", target_region);
}

View file

@ -2769,8 +2769,16 @@ fn instantiate_lambda_sets_as_unspecialized(
able_var: Variable,
ability_member: Symbol,
) {
// We want to pop and assign lambda sets pre-order for readability, so types
// should be pushed onto the stack in post-order
// REGION-ORDERING: done in pre-order via the following pseudo code:
//
// Type_function = \region ->
// let left_type, new_region = Type (region + 1)
// let right_type, new_region = Type (new_region)
// let func_type = left_type -[Lambda region]-> right_type
// (func_type, new_region)
//
// Since we want to pop types in pre-order, they should be pushed onto the
// stack in post-order
let mut stack = vec![typ];
let mut region = 0;

View file

@ -5,7 +5,7 @@ use roc_debug_flags::dbg_do;
use roc_debug_flags::{ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS};
use roc_error_macros::internal_error;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_module::symbol::{ModuleId, Symbol};
use roc_types::num::{FloatWidth, IntLitWidth, NumericRange};
use roc_types::subs::Content::{self, *};
use roc_types::subs::{
@ -754,6 +754,15 @@ fn unify_alias<M: MetaCollector>(
}
}
#[inline(always)]
fn opaque_obligation(opaque: Symbol, opaque_var: Variable) -> Obligated {
match opaque.module_id() {
// Numbers should be treated as ad-hoc obligations for ability checking.
ModuleId::NUM => Obligated::Adhoc(opaque_var),
_ => Obligated::Opaque(opaque),
}
}
#[inline(always)]
fn unify_opaque<M: MetaCollector>(
subs: &mut Subs,
@ -772,7 +781,7 @@ fn unify_opaque<M: MetaCollector>(
// Alias wins
merge(subs, ctx, Alias(symbol, args, real_var, kind))
}
FlexAbleVar(_, ability) if args.is_empty() => {
FlexAbleVar(_, ability) => {
// Opaque type wins
merge_flex_able_with_concrete(
subs,
@ -780,7 +789,7 @@ fn unify_opaque<M: MetaCollector>(
ctx.second,
*ability,
Alias(symbol, args, real_var, kind),
Obligated::Opaque(symbol),
opaque_obligation(symbol, ctx.first),
)
}
Alias(_, _, other_real_var, AliasKind::Structural) => {
@ -1062,12 +1071,12 @@ struct SeparatedUnionLambdas {
joined: Vec<(Symbol, VariableSubsSlice)>,
}
fn separate_union_lambdas(
fn separate_union_lambdas<M: MetaCollector>(
subs: &mut Subs,
pool: &mut Pool,
fields1: UnionLambdas,
fields2: UnionLambdas,
) -> SeparatedUnionLambdas {
) -> (Outcome<M>, SeparatedUnionLambdas) {
debug_assert!(
fields1.is_sorted_allow_duplicates(subs),
"not sorted: {:?}",
@ -1124,6 +1133,7 @@ fn separate_union_lambdas(
}
}
let mut whole_outcome = Outcome::default();
let mut only_in_left = Vec::with_capacity(fields1.len());
let mut only_in_right = Vec::with_capacity(fields2.len());
let mut joined = Vec::with_capacity(fields1.len() + fields2.len());
@ -1172,13 +1182,14 @@ fn separate_union_lambdas(
maybe_mark_union_recursive(subs, var1);
maybe_mark_union_recursive(subs, var2);
let outcome =
unify_pool::<NoCollector>(subs, pool, var1, var2, Mode::EQ);
let outcome = unify_pool(subs, pool, var1, var2, Mode::EQ);
if !outcome.mismatches.is_empty() {
subs.rollback_to(snapshot);
continue 'try_next_right;
}
whole_outcome.union(outcome);
}
// All the variables unified, so we can join the left + right.
@ -1202,11 +1213,14 @@ fn separate_union_lambdas(
}
}
(
whole_outcome,
SeparatedUnionLambdas {
only_in_left,
only_in_right,
joined,
}
},
)
}
fn unify_unspecialized_lambdas<M: MetaCollector>(
@ -1214,7 +1228,7 @@ fn unify_unspecialized_lambdas<M: MetaCollector>(
pool: &mut Pool,
uls1: SubsSlice<Uls>,
uls2: SubsSlice<Uls>,
) -> Result<SubsSlice<Uls>, Outcome<M>> {
) -> Result<(SubsSlice<Uls>, Outcome<M>), Outcome<M>> {
// For now we merge all variables of unspecialized lambdas in a lambda set that share the same
// ability member/region.
// See the section "A property that's lost, and how we can hold on to it" of
@ -1225,9 +1239,9 @@ fn unify_unspecialized_lambdas<M: MetaCollector>(
// resolves to itself or re-points to lset2.
// In either case the merged unspecialized lambda sets will be there.
match (uls1.is_empty(), uls2.is_empty()) {
(true, true) => Ok(SubsSlice::default()),
(false, true) => Ok(uls1),
(true, false) => Ok(uls2),
(true, true) => Ok((SubsSlice::default(), Default::default())),
(false, true) => Ok((uls1, Default::default())),
(true, false) => Ok((uls2, Default::default())),
(false, false) => {
let mut all_uls = (subs.get_subs_slice(uls1).iter())
.chain(subs.get_subs_slice(uls2))
@ -1241,6 +1255,7 @@ fn unify_unspecialized_lambdas<M: MetaCollector>(
// Now merge the variables of unspecialized lambdas pointing to the same
// member/region.
let mut whole_outcome = Outcome::default();
let mut j = 1;
while j < all_uls.len() {
let i = j - 1;
@ -1251,6 +1266,7 @@ fn unify_unspecialized_lambdas<M: MetaCollector>(
if !outcome.mismatches.is_empty() {
return Err(outcome);
}
whole_outcome.union(outcome);
// Keep the Uls in position `i` and remove the one in position `j`.
all_uls.remove(j);
} else {
@ -1259,9 +1275,9 @@ fn unify_unspecialized_lambdas<M: MetaCollector>(
}
}
Ok(SubsSlice::extend_new(
&mut subs.unspecialized_lambda_sets,
all_uls,
Ok((
SubsSlice::extend_new(&mut subs.unspecialized_lambda_sets, all_uls),
whole_outcome,
))
}
}
@ -1302,11 +1318,14 @@ fn unify_lambda_set_help<M: MetaCollector>(
"Recursion var is present, but it doesn't have a recursive content!"
);
let SeparatedUnionLambdas {
let (
mut whole_outcome,
SeparatedUnionLambdas {
only_in_left,
only_in_right,
joined,
} = separate_union_lambdas(subs, pool, solved1, solved2);
},
) = separate_union_lambdas(subs, pool, solved1, solved2);
let all_lambdas = joined
.into_iter()
@ -1334,7 +1353,10 @@ fn unify_lambda_set_help<M: MetaCollector>(
};
let merged_unspecialized = match unify_unspecialized_lambdas(subs, pool, uls1, uls2) {
Ok(merged) => merged,
Ok((merged, outcome)) => {
whole_outcome.union(outcome);
merged
}
Err(outcome) => {
debug_assert!(!outcome.mismatches.is_empty());
return outcome;
@ -1349,7 +1371,9 @@ fn unify_lambda_set_help<M: MetaCollector>(
ambient_function: ambient_function_var,
});
merge(subs, ctx, new_lambda_set)
let merge_outcome = merge(subs, ctx, new_lambda_set);
whole_outcome.union(merge_outcome);
whole_outcome
}
/// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive
@ -1882,6 +1906,8 @@ fn unify_tag_unions<M: MetaCollector>(
let flat_type = FlatType::TagUnion(unique_tags1, ext1);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let mut total_outcome = Outcome::default();
// In a presence context, we don't care about ext2 being equal to tags1
if ctx.mode.is_eq() {
let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
@ -1889,9 +1915,10 @@ fn unify_tag_unions<M: MetaCollector>(
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
total_outcome.union(ext_outcome);
}
unify_shared_tags_new(
let shared_tags_outcome = unify_shared_tags_new(
subs,
pool,
ctx,
@ -1899,7 +1926,9 @@ fn unify_tag_unions<M: MetaCollector>(
OtherTags2::Empty,
sub_record,
recursion_var,
)
);
total_outcome.union(shared_tags_outcome);
total_outcome
} else {
let other_tags = OtherTags2::Union(separate.only_in_1.clone(), separate.only_in_2.clone());
@ -1932,6 +1961,7 @@ fn unify_tag_unions<M: MetaCollector>(
// without rolling back, the mismatch is between `[Blue, Red, Green]a` and `[Red, Green]`.
// TODO is this also required for the other cases?
let mut total_outcome = Outcome::default();
let snapshot = subs.snapshot();
let ext1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
@ -1939,6 +1969,7 @@ fn unify_tag_unions<M: MetaCollector>(
subs.rollback_to(snapshot);
return ext1_outcome;
}
total_outcome.union(ext1_outcome);
if ctx.mode.is_eq() {
let ext2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
@ -1946,11 +1977,15 @@ fn unify_tag_unions<M: MetaCollector>(
subs.rollback_to(snapshot);
return ext2_outcome;
}
total_outcome.union(ext2_outcome);
}
subs.commit_snapshot(snapshot);
unify_shared_tags_new(subs, pool, ctx, shared_tags, other_tags, ext, recursion_var)
let shared_tags_outcome =
unify_shared_tags_new(subs, pool, ctx, shared_tags, other_tags, ext, recursion_var);
total_outcome.union(shared_tags_outcome);
total_outcome
}
}
@ -2015,6 +2050,8 @@ fn unify_shared_tags_new<M: MetaCollector>(
let mut matching_tags = Vec::default();
let num_shared_tags = shared_tags.len();
let mut total_outcome = Outcome::default();
for (name, (actual_vars, expected_vars)) in shared_tags {
let mut matching_vars = Vec::with_capacity(actual_vars.len());
@ -2069,6 +2106,8 @@ fn unify_shared_tags_new<M: MetaCollector>(
} else if outcome.mismatches.is_empty() {
matching_vars.push(actual);
}
total_outcome.union(outcome);
}
// only do this check after unification so the error message has more info
@ -2119,7 +2158,11 @@ fn unify_shared_tags_new<M: MetaCollector>(
}
};
unify_shared_tags_merge_new(subs, ctx, new_tags, new_ext_var, recursion_var)
let merge_outcome =
unify_shared_tags_merge_new(subs, ctx, new_tags, new_ext_var, recursion_var);
total_outcome.union(merge_outcome);
total_outcome
} else {
mismatch!(
"Problem with Tag Union\nThere should be {:?} matching tags, but I only got \n{:?}",
@ -2528,8 +2571,7 @@ fn unify_flex_able<M: MetaCollector>(
RecursionVar { .. } => mismatch!("FlexAble with RecursionVar"),
LambdaSet(..) => mismatch!("FlexAble with LambdaSet"),
Alias(name, args, _real_var, AliasKind::Opaque) => {
if args.is_empty() {
Alias(name, _args, _real_var, AliasKind::Opaque) => {
// Opaque type wins
merge_flex_able_with_concrete(
subs,
@ -2537,11 +2579,8 @@ fn unify_flex_able<M: MetaCollector>(
ctx.first,
ability,
*other,
Obligated::Opaque(*name),
opaque_obligation(*name, ctx.second),
)
} else {
mismatch!("FlexAble vs Opaque with type vars")
}
}
Structure(_) | Alias(_, _, _, AliasKind::Structural) | RangedNumber(..) => {

View file

@ -17,7 +17,7 @@ For convenience and consistency, there is only one way to format roc.
pub const HELLO_WORLD: &str = r#"
app "test-app"
packages { pf: "c-platform/main.roc" }
packages { pf: "platform/main.roc" }
imports []
provides [main] to pf
@ -29,7 +29,7 @@ pub fn nr_hello_world_lines() -> usize {
HELLO_WORLD.matches('\n').count() - 1
}
pub const PLATFORM_DIR_NAME: &str = "c-platform";
pub const PLATFORM_DIR_NAME: &str = "platform";
pub const PLATFORM_FILE_NAME: &str = "main.roc";
pub const PLATFORM_STR: &str = r#"

View file

@ -3,7 +3,6 @@ struct VertexOutput {
[[builtin(position)]] position: vec4<f32>;
};
[[block]]
struct Globals {
ortho: mat4x4<f32>;
};

View file

@ -11,21 +11,17 @@ description = "A surgical linker for Roc"
name = "roc_linker"
path = "src/lib.rs"
[[bin]]
name = "link"
path = "src/main.rs"
test = false
bench = false
[dependencies]
roc_mono = { path = "../compiler/mono" }
roc_build = { path = "../compiler/build" }
roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" }
bumpalo = { version = "3.8.0", features = ["collections"] }
clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] }
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
memmap2 = "0.5.3"
object = { version = "0.29.0", features = ["read", "write"] }
object = { version = "0.26.2", features = ["read", "write"] }
mach_object = "0.1"
serde = { version = "1.0.130", features = ["derive"] }
bincode = "1.3.3"
target-lexicon = "0.12.3"

View file

@ -39,7 +39,7 @@ This linker is run in 2 phases: preprocessing and surigical linking.
- As a prereq, we need roc building on Windows (I'm not sure it does currently).
- Definitely a solid bit different than elf, but hopefully after refactoring for Macho, won't be that crazy to add.
- Look at enabling completely in memory linking that could be used with `roc run` and/or `roc repl`
- Look more into roc hosts and keeping certain functions. Currently I just disabled linker garbage collection.
- Look more into rust hosts and keeping certain functions. Currently I just disabled linker garbage collection.
This works but adds 1.2MB (40%) to even a tiny app. It may be a size issue for large rust hosts.
Roc, for reference, adds 13MB (20%) when linked without garbage collection.
- Add a feature to the compiler to make this linker optional.

File diff suppressed because it is too large Load diff

View file

@ -1,14 +0,0 @@
use roc_linker::{build_app, preprocess, surgery, CMD_PREPROCESS, CMD_SURGERY};
use std::io;
fn main() -> io::Result<()> {
let matches = build_app().get_matches();
let exit_code = match matches.subcommand() {
None => Ok::<i32, io::Error>(-1),
Some((CMD_PREPROCESS, sub_matches)) => preprocess(sub_matches),
Some((CMD_SURGERY, sub_matches)) => surgery(sub_matches),
_ => unreachable!(),
}?;
std::process::exit(exit_code);
}

View file

@ -34,4 +34,5 @@ pub struct Metadata {
pub dynamic_symbol_table_section_offset: u64,
pub symbol_table_section_offset: u64,
pub symbol_table_size: u64,
pub macho_cmd_loc: u64,
}

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