Merge remote-tracking branch 'origin/main' into expect-fx-codegen

This commit is contained in:
Folkert 2022-08-23 16:28:21 +02:00
commit a22e04361c
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
222 changed files with 10039 additions and 1945 deletions

View file

@ -172,7 +172,7 @@ ask the compiler to emit debug information during various stages of compilation.
There are some goals for more sophisticated debugging tools:
- A nicer unification debugger, see https://github.com/rtfeldman/roc/issues/2486.
- A nicer unification debugger, see https://github.com/roc-lang/roc/issues/2486.
Any interest in helping out here is greatly appreciated.
### General Tips

View file

@ -3,6 +3,6 @@ name = "arena-pool"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "A CLI for Roc"

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod link;
pub mod program;

View file

@ -113,7 +113,6 @@ pub fn build_zig_host_native(
target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
_target_valgrind: bool,
) -> Output {
let mut command = Command::new(&zig_executable());
command
@ -149,13 +148,10 @@ pub fn build_zig_host_native(
target,
]);
// use single threaded testing for cli_run and enable this code if valgrind fails with unhandled instruction bytes, see #1963.
/*if target_valgrind {
command.args(&[
"-mcpu",
"x86_64"
]);
}*/
// valgrind does not yet support avx512 instructions, see #1963.
if env::var("NO_AVX512").is_ok() {
command.args(&["-mcpu", "x86_64"]);
}
if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
@ -177,7 +173,6 @@ pub fn build_zig_host_native(
target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
_target_valgrind: bool,
) -> Output {
let mut command = Command::new(&zig_executable());
command
@ -234,7 +229,6 @@ pub fn build_zig_host_native(
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
// For compatibility with the non-macOS def above. Keep these in sync.
_target_valgrind: bool,
) -> Output {
use serde_json::Value;
@ -463,7 +457,6 @@ pub fn rebuild_host(
target: &Triple,
host_input_path: &Path,
shared_lib_path: Option<&Path>,
target_valgrind: bool,
) -> PathBuf {
let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o");
@ -535,7 +528,6 @@ pub fn rebuild_host(
"native",
opt_level,
shared_lib_path,
target_valgrind,
)
}
Architecture::X86_32(_) => {
@ -549,7 +541,6 @@ pub fn rebuild_host(
"i386-linux-musl",
opt_level,
shared_lib_path,
target_valgrind,
)
}
@ -564,7 +555,6 @@ pub fn rebuild_host(
target_zig_str(target),
opt_level,
shared_lib_path,
target_valgrind,
)
}
_ => panic!("Unsupported architecture {:?}", target.architecture),
@ -971,7 +961,7 @@ fn link_linux(
// ld.lld requires this argument, and does not accept --arch
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496365925
// for discussion and further references
"-lc",
"-lm",
@ -1054,7 +1044,7 @@ fn link_macos(
}
ld_command.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496392274
// for discussion and further references
"-lSystem",
"-lresolv",
@ -1079,7 +1069,7 @@ fn link_macos(
"QuartzCore",
// "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // TODO shouldn't we need this?
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/roc-lang/roc/pull/554#discussion_r496370840
"-framework",
"Security",
// Output

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -euxo pipefail

View file

@ -1,9 +1,10 @@
#!/bin/bash
#!/usr/bin/env bash
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
# Test every zig
# Execute zig tests (see build.zig)
zig build test
# fmt every zig
# check formatting of zig files
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check || (echo "zig fmt --check FAILED! Check the previous lines to see which files were improperly formatted." && exit 1)

View file

@ -1,5 +1,6 @@
#!/bin/bash
#!/usr/bin/env bash
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
# Test failures will always point at the _start function

View file

@ -181,7 +181,7 @@ comptime {
// Utils continued - SJLJ
// For tests (in particular test_gen), roc_panic is implemented in terms of
// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/rtfeldman/roc/issues/2965),
// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/roc-lang/roc/issues/2965),
// so instead we ask Zig to please provide implementations for us, which is does
// (seemingly via musl).
pub extern fn setjmp([*c]c_int) c_int;

View file

@ -22,6 +22,7 @@ interface Decode
bool,
string,
list,
record,
custom,
decodeWith,
fromBytesPartial,
@ -57,6 +58,7 @@ DecoderFormatting has
bool : Decoder Bool fmt | fmt has DecoderFormatting
string : Decoder Str fmt | fmt has DecoderFormatting
list : Decoder elem fmt -> Decoder (List elem) fmt | fmt has DecoderFormatting
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting
custom : (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
custom = \decode -> @Decoder decode

View file

@ -16,6 +16,7 @@ interface Json
Decode,
Decode.{
DecoderFormatting,
DecodeResult,
},
]
@ -57,6 +58,7 @@ Json := {} has [
bool: decodeBool,
string: decodeString,
list: decodeList,
record: decodeRecord,
},
]
@ -316,7 +318,8 @@ decodeBool = Decode.custom \bytes, @Json {} ->
else
{ result: Err TooShort, rest: bytes }
decodeString = Decode.custom \bytes, @Json {} ->
jsonString : List U8 -> DecodeResult Str
jsonString = \bytes ->
{ before, others: afterStartingQuote } = List.split bytes 1
if
@ -335,6 +338,9 @@ decodeString = Decode.custom \bytes, @Json {} ->
else
{ result: Err TooShort, rest: bytes }
decodeString = Decode.custom \bytes, @Json {} ->
jsonString bytes
decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
decodeElems = \chunk, accum ->
when Decode.decodeWith chunk decodeElem (@Json {}) is
@ -372,3 +378,72 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
{ result: Err TooShort, rest }
else
{ result: Err TooShort, rest: bytes }
parseExactChar : List U8, U8 -> DecodeResult {}
parseExactChar = \bytes, char ->
when List.get bytes 0 is
Ok c ->
if
c == char
then
{ result: Ok {}, rest: (List.split bytes 1).others }
else
{ result: Err TooShort, rest: bytes }
Err _ -> { result: Err TooShort, rest: bytes }
openBrace : List U8 -> DecodeResult {}
openBrace = \bytes -> parseExactChar bytes (asciiByte '{')
closingBrace : List U8 -> DecodeResult {}
closingBrace = \bytes -> parseExactChar bytes (asciiByte '}')
recordKey : List U8 -> DecodeResult Str
recordKey = \bytes -> jsonString bytes
anything : List U8 -> DecodeResult {}
anything = \bytes -> { result: Err TooShort, rest: bytes }
colon : List U8 -> DecodeResult {}
colon = \bytes -> parseExactChar bytes (asciiByte ':')
comma : List U8 -> DecodeResult {}
comma = \bytes -> parseExactChar bytes (asciiByte ',')
tryDecode : DecodeResult a, ({ val : a, rest : List U8 } -> DecodeResult b) -> DecodeResult b
tryDecode = \{ result, rest }, mapper ->
when result is
Ok val -> mapper { val, rest }
Err e -> { result: Err e, rest }
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json {} ->
# NB: the stepper function must be passed explicitly until #2894 is resolved.
decodeFields = \stepper, state, kvBytes ->
{ val: key, rest } <- recordKey kvBytes |> tryDecode
{ rest: afterColonBytes } <- colon rest |> tryDecode
{ val: newState, rest: beforeCommaOrBreak } <- tryDecode
(
when stepper state key is
Skip ->
{ rest: beforeCommaOrBreak } <- afterColonBytes |> anything |> tryDecode
{ result: Ok state, rest: beforeCommaOrBreak }
Keep decoder ->
Decode.decodeWith afterColonBytes decoder (@Json {})
)
{ result: commaResult, rest: nextBytes } = comma beforeCommaOrBreak
when commaResult is
Ok {} -> decodeFields stepField newState nextBytes
Err _ -> { result: Ok newState, rest: nextBytes }
{ rest: afterBraceBytes } <- bytes |> openBrace |> tryDecode
{ val: endStateResult, rest: beforeClosingBraceBytes } <- decodeFields stepField initialState afterBraceBytes |> tryDecode
{ rest: afterRecordBytes } <- beforeClosingBraceBytes |> closingBrace |> tryDecode
when finalizer endStateResult is
Ok val -> { result: Ok val, rest: afterRecordBytes }
Err e -> { result: Err e, rest: afterRecordBytes }

View file

@ -857,8 +857,44 @@ isMultipleOf : Int a, Int a -> Bool
bitwiseAnd : Int a, Int a -> Int a
bitwiseXor : Int a, Int a -> Int a
bitwiseOr : Int a, Int a -> Int a
## Bitwise left shift of a number by another
##
## The least significant bits always become 0. This means that shifting left is
## like multiplying by factors of two for unsigned integers.
##
## >>> shiftLeftBy 0b0000_0011 2 == 0b0000_1100
##
## >>> 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
##
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
shiftLeftBy : Int a, Int a -> Int a
## Bitwise arithmetic shift of a number by another
##
## The most significant bits are copied from the current.
##
## >>> shiftRightBy 0b0000_0011 2 == 0b0000_1100
##
## >>> 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
##
## In some languages `shiftRightBy` is implemented as a binary operator `>>>`.
shiftRightBy : Int a, Int a -> Int a
## Bitwise logical right shift of a number by another
##
## The most significant bits always become 0. This means that shifting left is
## like dividing by factors of two for unsigned integers.
##
## >>> shiftRightBy 0b0010_1000 2 == 0b0000_1010
##
## >>> 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
##
## In some languages `shiftRightBy` is implemented as a binary operator `>>`.
shiftRightZfBy : Int a, Int a -> Int a
## Round off the given fraction to the nearest integer.

View file

@ -41,10 +41,39 @@ insert = \@Set dict, key ->
|> Dict.insert key {}
|> @Set
# Inserting a duplicate key has no effect.
expect
actual =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"
expected =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "baz"
expected == actual
len : Set k -> Nat
len = \@Set dict ->
Dict.len dict
# Inserting a duplicate key has no effect on length.
expect
actual =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"
|> Set.len
actual == 3
## Drops the given element from the set.
remove : Set k, k -> Set k
remove = \@Set dict, key ->

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod bitcode;
pub mod roc;

View file

@ -20,4 +20,4 @@ bitvec = "1"
[dev-dependencies]
pretty_assertions = "1.0.0"
indoc = "1.0.3"
indoc = "1.0.7"

View file

@ -444,7 +444,7 @@ fn no_region<T>(value: T) -> Loc<T> {
#[inline(always)]
fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
Expr::Tag {
variant_var: var_store.fresh(),
tag_union_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName(name.into()),
arguments: args

View file

@ -535,12 +535,12 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
},
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,
} => Tag {
variant_var: sub!(*variant_var),
tag_union_var: sub!(*variant_var),
ext_var: sub!(*ext_var),
name: name.clone(),
arguments: arguments
@ -1164,13 +1164,13 @@ mod test {
let var2 = new_var(&mut subs, FlexVar(Some(b)));
let expr = Expr::Tag {
variant_var: var1,
tag_union_var: var1,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("F".into()),
arguments: vec![(
var2,
Loc::at_zero(Expr::Tag {
variant_var: var2,
tag_union_var: var2,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("G".into()),
arguments: vec![],
@ -1185,7 +1185,7 @@ mod test {
match expr {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
mut arguments,
@ -1219,7 +1219,7 @@ mod test {
match arg.value {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,
@ -1250,13 +1250,13 @@ mod test {
let var2 = new_var(&mut source, FlexVar(Some(b)));
let expr = Expr::Tag {
variant_var: var1,
tag_union_var: var1,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("F".into()),
arguments: vec![(
var2,
Loc::at_zero(Expr::Tag {
variant_var: var2,
tag_union_var: var2,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("G".into()),
arguments: vec![],
@ -1271,7 +1271,7 @@ mod test {
match expr {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
mut arguments,
@ -1300,7 +1300,7 @@ mod test {
match arg.value {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,

View file

@ -232,7 +232,7 @@ impl PendingTypeDef<'_> {
}
}
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Declaration {

View file

@ -184,7 +184,7 @@ pub enum Expr {
// Sum Types
Tag {
variant_var: Variable,
tag_union_var: Variable,
ext_var: Variable,
name: TagName,
arguments: Vec<(Variable, Loc<Expr>)>,
@ -780,12 +780,12 @@ pub fn canonicalize_expr<'a>(
return (fn_expr, output);
}
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
..
} => Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments: args,
@ -796,7 +796,7 @@ pub fn canonicalize_expr<'a>(
name,
..
} => Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments: args,
@ -1422,7 +1422,7 @@ fn canonicalize_when_branch<'a>(
if output.references.has_value_lookup(symbol) {
pattern_bound_symbols_body_needs.insert(symbol);
} else {
env.problem(Problem::UnusedDef(symbol, region));
env.problem(Problem::UnusedBranchDef(symbol, region));
}
}
@ -1893,7 +1893,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
}
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod abilities;
pub mod annotation;

View file

@ -696,15 +696,26 @@ pub fn canonicalize_module_defs<'a>(
referenced_values.extend(env.qualified_value_lookups.iter().copied());
referenced_types.extend(env.qualified_type_lookups.iter().copied());
let mut fix_closures_no_capture_symbols = VecSet::default();
let mut fix_closures_closure_captures = VecMap::default();
for index in 0..declarations.len() {
use crate::expr::DeclarationTag::*;
// For each declaration, we need to fixup the closures inside its def.
// Reuse the fixup buffer allocations from the previous iteration.
fix_closures_no_capture_symbols.clear();
fix_closures_closure_captures.clear();
match declarations.declarations[index] {
Value => {
// def pattern has no default expressions, so skip
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
Function(f_index) | Recursive(f_index) | TailRecursive(f_index) => {
let name = declarations.symbols[index].value;
@ -722,30 +733,51 @@ pub fn canonicalize_module_defs<'a>(
for (_, _, loc_pat) in function_def.arguments.iter_mut() {
fix_values_captured_in_closure_pattern(
&mut loc_pat.value,
&mut no_capture_symbols,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
Destructure(d_index) => {
let destruct_def = &mut declarations.destructs[d_index.index()];
let loc_pat = &mut destruct_def.loc_pattern;
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_pattern(&mut loc_pat.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_pattern(
&mut loc_pat.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
MutualRecursion { .. } => {
// the declarations of this group will be treaded individually by later iterations
}
Expectation => {
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
ExpectationFx => {
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
}
}
@ -771,16 +803,26 @@ pub fn canonicalize_module_defs<'a>(
fn fix_values_captured_in_closure_def(
def: &mut crate::def::Def,
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
// patterns can contain default expressions, so much go over them too!
fix_values_captured_in_closure_pattern(&mut def.loc_pattern.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut def.loc_pattern.value,
no_capture_symbols,
closure_captures,
);
fix_values_captured_in_closure_expr(&mut def.loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut def.loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
fn fix_values_captured_in_closure_defs(
defs: &mut [crate::def::Def],
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
@ -789,16 +831,38 @@ fn fix_values_captured_in_closure_defs(
);
}
// TODO mutually recursive functions should both capture the union of both their capture sets
for def in defs.iter_mut() {
fix_values_captured_in_closure_def(def, no_capture_symbols);
fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
}
// Mutually recursive functions should both capture the union of all their capture sets
//
// Really unfortunate we make a lot of clones here, can this be done more efficiently?
let mut total_capture_set = Vec::default();
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
}) = &def.loc_expr.value
{
total_capture_set.extend(captured_symbols.iter().copied());
}
}
total_capture_set.sort_by_key(|(sym, _)| *sym);
total_capture_set.dedup_by_key(|(sym, _)| *sym);
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
}) = &mut def.loc_expr.value
{
*captured_symbols = total_capture_set.clone();
}
}
}
fn fix_values_captured_in_closure_pattern(
pattern: &mut crate::pattern::Pattern,
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
use crate::pattern::Pattern::*;
@ -808,24 +872,35 @@ fn fix_values_captured_in_closure_pattern(
..
} => {
for (_, loc_arg) in loc_args.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
}
UnwrappedOpaque { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
RecordDestructure { destructs, .. } => {
for loc_destruct in destructs.iter_mut() {
use crate::pattern::DestructType::*;
match &mut loc_destruct.value.typ {
Required => {}
Optional(_, loc_expr) => {
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols)
}
Optional(_, loc_expr) => fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
),
Guard(_, loc_pattern) => fix_values_captured_in_closure_pattern(
&mut loc_pattern.value,
no_capture_symbols,
closure_captures,
),
}
}
@ -848,19 +923,28 @@ fn fix_values_captured_in_closure_pattern(
fn fix_values_captured_in_closure_expr(
expr: &mut crate::expr::Expr,
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
use crate::expr::Expr::*;
match expr {
LetNonRec(def, loc_expr) => {
// LetNonRec(Box<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_def(def, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
LetRec(defs, loc_expr, _) => {
// LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_defs(defs, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_defs(defs, no_capture_symbols, closure_captures);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
Expect {
@ -868,8 +952,16 @@ fn fix_values_captured_in_closure_expr(
loc_continuation,
lookups_in_cond: _,
} => {
fix_values_captured_in_closure_expr(&mut loc_condition.value, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_continuation.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_condition.value,
no_capture_symbols,
closure_captures,
);
fix_values_captured_in_closure_expr(
&mut loc_continuation.value,
no_capture_symbols,
closure_captures,
);
}
ExpectFx {
@ -891,16 +983,58 @@ fn fix_values_captured_in_closure_expr(
captured_symbols.retain(|(s, _)| !no_capture_symbols.contains(s));
captured_symbols.retain(|(s, _)| s != name);
let original_captures_len = captured_symbols.len();
let mut num_visited = 0;
let mut i = 0;
while num_visited < original_captures_len {
// If we've captured a capturing closure, replace the captured closure symbol with
// the symbols of its captures. That way, we can construct the closure with the
// captures it needs inside our body.
//
// E.g.
// x = ""
// inner = \{} -> x
// outer = \{} -> inner {}
//
// initially `outer` captures [inner], but this is then replaced with just [x].
let (captured_symbol, _) = captured_symbols[i];
if let Some(captures) = closure_captures.get(&captured_symbol) {
debug_assert!(!captures.is_empty());
captured_symbols.swap_remove(i);
captured_symbols.extend(captures);
// Jump two, because the next element is now one of the newly-added captures,
// which we don't need to check.
i += 2;
} else {
i += 1;
}
num_visited += 1;
}
if captured_symbols.len() > original_captures_len {
// Re-sort, since we've added new captures.
captured_symbols.sort_by_key(|(sym, _)| *sym);
}
if captured_symbols.is_empty() {
no_capture_symbols.insert(*name);
} else {
closure_captures.insert(*name, captured_symbols.to_vec());
}
// patterns can contain default expressions, so much go over them too!
for (_, _, loc_pat) in arguments.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_pat.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut loc_pat.value,
no_capture_symbols,
closure_captures,
);
}
fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_body.value,
no_capture_symbols,
closure_captures,
);
}
Num(..)
@ -918,28 +1052,45 @@ fn fix_values_captured_in_closure_expr(
List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() {
fix_values_captured_in_closure_expr(&mut elem.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut elem.value,
no_capture_symbols,
closure_captures,
);
}
}
When {
loc_cond, branches, ..
} => {
fix_values_captured_in_closure_expr(&mut loc_cond.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_cond.value,
no_capture_symbols,
closure_captures,
);
for branch in branches.iter_mut() {
fix_values_captured_in_closure_expr(&mut branch.value.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut branch.value.value,
no_capture_symbols,
closure_captures,
);
// patterns can contain default expressions, so much go over them too!
for loc_pat in branch.patterns.iter_mut() {
fix_values_captured_in_closure_pattern(
&mut loc_pat.pattern.value,
no_capture_symbols,
closure_captures,
);
}
if let Some(guard) = &mut branch.guard {
fix_values_captured_in_closure_expr(&mut guard.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut guard.value,
no_capture_symbols,
closure_captures,
);
}
}
}
@ -950,23 +1101,43 @@ fn fix_values_captured_in_closure_expr(
..
} => {
for (loc_cond, loc_then) in branches.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_cond.value, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_then.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_cond.value,
no_capture_symbols,
closure_captures,
);
fix_values_captured_in_closure_expr(
&mut loc_then.value,
no_capture_symbols,
closure_captures,
);
}
fix_values_captured_in_closure_expr(&mut final_else.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut final_else.value,
no_capture_symbols,
closure_captures,
);
}
Call(function, arguments, _) => {
fix_values_captured_in_closure_expr(&mut function.1.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut function.1.value,
no_capture_symbols,
closure_captures,
);
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
}
RunLowLevel { args, .. } | ForeignCall { args, .. } => {
for (_, arg) in args.iter_mut() {
fix_values_captured_in_closure_expr(arg, no_capture_symbols);
fix_values_captured_in_closure_expr(arg, no_capture_symbols, closure_captures);
}
}
@ -975,22 +1146,38 @@ fn fix_values_captured_in_closure_expr(
updates: fields, ..
} => {
for (_, field) in fields.iter_mut() {
fix_values_captured_in_closure_expr(&mut field.loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut field.loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
}
Access { loc_expr, .. } => {
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
Tag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
}
OpaqueRef { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
OpaqueWrapFunction(_) => {}
}

View file

@ -244,7 +244,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
walk_record_fields(visitor, updates.iter());
}
Expr::Tag {
variant_var: _,
tag_union_var: _,
ext_var: _,
name: _,
arguments,

View file

@ -1023,7 +1023,7 @@ mod test_can {
# There was a bug where annotating a def meant that its
# references no longer got reported.
#
# https://github.com/rtfeldman/roc/issues/298
# https://github.com/roc-lang/roc/issues/298
x : List Booly
x = [y]

View file

@ -10,5 +10,5 @@ im = "15.0.0"
im-rc = "15.0.0"
wyhash = "0.5.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.12.1", features = [ "bumpalo" ] }
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
bitvec = "1"

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod all;

View file

@ -140,6 +140,12 @@ impl<K: PartialEq, V> VecMap<K, V> {
cur_idx: 0,
}
}
/// Removes all key/value pairs from the map, without affecting its allocated capacity.
pub fn clear(&mut self) {
self.keys.clear();
self.values.clear();
}
}
impl<K: PartialEq, V> Extend<(K, V)> for VecMap<K, V> {

View file

@ -83,6 +83,11 @@ impl<T: PartialEq> VecSet<T> {
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.elements.iter_mut()
}
/// Removes all elements from the set, without affecting its allocated capacity.
pub fn clear(&mut self) {
self.elements.clear()
}
}
impl<A: Ord> Extend<A> for VecSet<A> {

View file

@ -1040,7 +1040,7 @@ pub fn constrain_expr(
body_con
}
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod builtins;
pub mod expr;

View file

@ -19,3 +19,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
[features]
default = []
debug-derived-symbols = ["roc_module/debug-symbols"]
# Enables open extension variables for constructed records and tag unions.
# This is not necessary for code generation, but may be necessary if you are
# constraining and solving generated derived bodies.
open-extension-vars = []

File diff suppressed because it is too large Load diff

View file

@ -133,6 +133,12 @@ impl DerivedModule {
self.map.entry(key).or_insert(triple)
}
pub fn is_derived_def(&self, def_symbol: Symbol) -> bool {
self.map
.iter()
.any(|(_, (symbol, _, _))| *symbol == def_symbol)
}
pub fn iter_all(
&self,
) -> impl Iterator<Item = (&DeriveKey, &(Symbol, Def, SpecializationLambdaSets))> {

View file

@ -130,7 +130,7 @@ impl Env<'_> {
// we only expect `bar` to polymorphic at this stage!
//
// TODO: it would be better if `unify` could prune these for us. See also
// https://github.com/rtfeldman/roc/issues/3207; that is a blocker for this TODO.
// https://github.com/roc-lang/roc/issues/3207; that is a blocker for this TODO.
#[cfg(debug_assertions)]
{
for (spec_var, lambda_sets) in _lambda_sets_to_specialize.drain() {
@ -152,4 +152,27 @@ impl Env<'_> {
}
}
}
/// Creates an extension variable for a tag union or record.
///
/// Derivers should always construct tag union and record types such that they are closed.
/// If the `open-extension-vars` feature is turned on, flex extension vars will be
/// returned; otherwise, the appropriate closed extension variable for the type will be
/// returned.
#[inline(always)]
pub fn new_ext_var(&mut self, kind: ExtensionKind) -> Variable {
if cfg!(feature = "open-extension-vars") {
self.subs.fresh_unnamed_flex_var()
} else {
match kind {
ExtensionKind::Record => Variable::EMPTY_RECORD,
ExtensionKind::TagUnion => Variable::EMPTY_TAG_UNION,
}
}
}
}
pub(crate) enum ExtensionKind {
Record,
TagUnion,
}

View file

@ -1,7 +1,7 @@
use roc_module::symbol::Symbol;
use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable};
use crate::DeriveError;
use crate::{util::debug_name_record, DeriveError};
#[derive(Hash)]
pub enum FlatDecodable {
@ -12,12 +12,16 @@ pub enum FlatDecodable {
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
pub enum FlatDecodableKey {
List(/* takes one variable */),
// Unfortunate that we must allocate here, c'est la vie
Record(Vec<Lowercase>),
}
impl FlatDecodableKey {
pub(crate) fn debug_name(&self) -> String {
match self {
FlatDecodableKey::List() => "list".to_string(),
FlatDecodableKey::Record(fields) => debug_name_record(fields),
}
}
}
@ -33,8 +37,25 @@ impl FlatDecodable {
Symbol::STR_STR => Ok(Immediate(Symbol::DECODE_STRING)),
_ => Err(Underivable),
},
FlatType::Record(_fields, _ext) => {
Err(Underivable) // yet
FlatType::Record(fields, ext) => {
let fields_iter = match fields.unsorted_iterator(subs, ext) {
Ok(it) => it,
Err(_) => return Err(Underivable),
};
let mut field_names = Vec::with_capacity(fields.len());
for (field_name, record_field) in fields_iter {
if record_field.is_optional() {
// Can't derive a concrete decoder for optional fields, since those are
// compile-time-polymorphic
return Err(Underivable);
}
field_names.push(field_name.clone());
}
field_names.sort();
Ok(Key(FlatDecodableKey::Record(field_names)))
}
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
Err(Underivable) // yet
@ -42,9 +63,7 @@ impl FlatDecodable {
FlatType::FunctionOrTagUnion(_name_index, _, _) => {
Err(Underivable) // yet
}
FlatType::EmptyRecord => {
Err(Underivable) // yet
}
FlatType::EmptyRecord => Ok(Key(FlatDecodableKey::Record(vec![]))),
FlatType::EmptyTagUnion => {
Err(Underivable) // yet
}

View file

@ -4,7 +4,10 @@ use roc_module::{
};
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
use crate::DeriveError;
use crate::{
util::{check_empty_ext_var, debug_name_record},
DeriveError,
};
#[derive(Hash)]
pub enum FlatEncodable {
@ -28,17 +31,7 @@ impl FlatEncodableKey {
FlatEncodableKey::List() => "list".to_string(),
FlatEncodableKey::Set() => "set".to_string(),
FlatEncodableKey::Dict() => "dict".to_string(),
FlatEncodableKey::Record(fields) => {
let mut str = String::from('{');
fields.iter().enumerate().for_each(|(i, f)| {
if i > 0 {
str.push(',');
}
str.push_str(f.as_str());
});
str.push('}');
str
}
FlatEncodableKey::Record(fields) => debug_name_record(fields),
FlatEncodableKey::TagUnion(tags) => {
let mut str = String::from('[');
tags.iter().enumerate().for_each(|(i, (tag, arity))| {
@ -56,22 +49,6 @@ impl FlatEncodableKey {
}
}
fn check_ext_var(
subs: &Subs,
ext_var: Variable,
is_empty_ext: impl Fn(&Content) -> bool,
) -> Result<(), DeriveError> {
let ext_content = subs.get_content_without_compacting(ext_var);
if is_empty_ext(ext_content) {
Ok(())
} else {
match ext_content {
Content::FlexVar(_) => Err(DeriveError::UnboundVar),
_ => Err(DeriveError::Underivable),
}
}
}
impl FlatEncodable {
pub(crate) fn from_var(subs: &Subs, var: Variable) -> Result<FlatEncodable, DeriveError> {
use DeriveError::*;
@ -86,7 +63,7 @@ impl FlatEncodable {
_ => Err(Underivable),
},
FlatType::Record(fields, ext) => {
check_ext_var(subs, ext, |ext| {
check_empty_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyRecord))
})?;
@ -106,7 +83,7 @@ impl FlatEncodable {
// [ A t1, B t1 t2 ] as R
// look the same on the surface, because `R` is only somewhere inside of the
// `t`-prefixed payload types.
check_ext_var(subs, ext, |ext| {
check_empty_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTagUnion))
})?;

View file

@ -15,6 +15,7 @@
pub mod decoding;
pub mod encoding;
mod util;
use decoding::{FlatDecodable, FlatDecodableKey};
use encoding::{FlatEncodable, FlatEncodableKey};

View file

@ -0,0 +1,32 @@
use roc_module::ident::Lowercase;
use roc_types::subs::{Content, Subs, Variable};
use crate::DeriveError;
pub(crate) fn check_empty_ext_var(
subs: &Subs,
ext_var: Variable,
is_empty_ext: impl Fn(&Content) -> bool,
) -> Result<(), DeriveError> {
let ext_content = subs.get_content_without_compacting(ext_var);
if is_empty_ext(ext_content) {
Ok(())
} else {
match ext_content {
Content::FlexVar(_) => Err(DeriveError::UnboundVar),
_ => Err(DeriveError::Underivable),
}
}
}
pub(crate) fn debug_name_record(fields: &[Lowercase]) -> String {
let mut str = String::from('{');
fields.iter().enumerate().for_each(|(i, f)| {
if i > 0 {
str.push(',');
}
str.push_str(f.as_str());
});
str.push('}');
str
}

View file

@ -14,6 +14,6 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "1.0.0"
indoc = "1.0.3"
indoc = "1.0.7"
roc_test_utils = { path = "../../test_utils" }
walkdir = "2.3.2"

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod annotation;
pub mod collection;

View file

@ -1,7 +1,7 @@
# Dev Backend
The dev backend is focused on generating decent binaries extremely fast.
It goes from Roc's [mono ir](https://github.com/rtfeldman/roc/blob/trunk/compiler/mono/src/ir.rs) to an object file ready to be linked.
It goes from Roc's [mono ir](https://github.com/roc-lang/roc/blob/main/crates/compiler/mono/src/ir.rs) to an object file ready to be linked.
## General Process
@ -22,14 +22,14 @@ rust should be abled compile each specific target (`linux-arm`, `darwin-x86_64`,
### Backend
[Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) is the core abstraction.
[Backend](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/lib.rs) is the core abstraction.
It understands Roc's mono ir and some high level ideas about the generation process.
The main job of Backend is to do high level optimizatons (like lazy literal loading) and parse the mono ir.
Every target specific backend must implement this trait.
### Backend64Bit
[Backend64Bit](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is more or less what it sounds like.
[Backend64Bit](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs) is more or less what it sounds like.
It is the backend that understands 64 bit architectures.
Currently it is the only backend implementation, but a 32 bit implementation will probably come in the future.
This backend understands that the unit of data movement is 64 bit.
@ -44,39 +44,39 @@ Backend64Bit is generic over these types instead of containing these types withi
### Assembler
[Assembler](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the trait for generating assembly bytes.
[Assembler](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs) is the trait for generating assembly bytes.
It defines a set of RISC-like assembly calls that must be implemented for each architecture.
A lot of these calls may not map one to one with actual assembly instructions for each architecture.
Instead, they are a general abstraction over functionality shared between all architectures.
This will grow regularly as more Roc builtins are added.
Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/x86_64.rs).
Here are example implementations for [arm](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/x86_64.rs).
### CallConv
[CallConv](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the abstraction over calling conventions.
[CallConv](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs) is the abstraction over calling conventions.
It deals with register and stack specific information related to passing and returning arguments.
Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/x86_64.rs).
Here are example implementations for [arm](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/x86_64.rs).
## Adding New Features
Adding a new builtin to the dev backend can be pretty simple.
Here is [an example](https://github.com/rtfeldman/roc/pull/893/files) of adding `Num.Sub`.
Here is [an example](https://github.com/roc-lang/roc/pull/893/files) of adding `Num.Sub`.
This is the general procedure I follow with some helpful links:
1. Find a feature that is just n+1.
For example, since we already have integers, adding a builtin that functions on them should be n+1.
On the other hand, since we don't yet have booleans/conditionals, adding if statements may not yet be n+1.
A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/rtfeldman/roc/tree/trunk/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend.
A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/roc-lang/roc/tree/main/crates/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend.
1. Pick/write the simplest test case you can find for the new feature.
Just add `feature = "gen-dev"` to the `cfg` line for the test case.
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/b03ed18553569314a420d5bf1fb0ead4b6b5ecda/compiler/test_gen/src/helpers/dev.rs#L76) and run the test.
1. Uncomment the code to print out procedures [from here](https://github.com/roc-lang/roc/blob/b03ed18553569314a420d5bf1fb0ead4b6b5ecda/compiler/test_gen/src/helpers/dev.rs#L76) and run the test.
It should fail and print out the mono ir for this test case.
Seeing the actual mono ir tends to be very helpful for complex additions.
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/lib.rs) trait.
Add the correct pattern matching and likely new function for your new builtin.
This will break the compile until you add the same function to places that implement the trait,
like [Backend64Bit](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs).
like [Backend64Bit](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs).
1. Keep following the chain down.
To implement the function in Backend64Bit, you may need to add new assembly calls.
Feel free to ignore backends that aren't x86_64 for now and just add `unimplemented!`.

View file

@ -440,7 +440,39 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("register multiplication for AArch64");
todo!("register signed multiplication for AArch64");
}
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("register unsigned multiplication for AArch64");
}
#[inline(always)]
fn mul_freg32_freg32_freg32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("multiplication for floats for AArch64");
}
#[inline(always)]
fn mul_freg64_freg64_freg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("multiplication for floats for AArch64");
}
#[inline(always)]

View file

@ -21,7 +21,7 @@ mod disassembler_test_macro;
pub(crate) mod storage;
pub(crate) mod x86_64;
use storage::StorageManager;
use storage::{RegStorage, StorageManager};
const REFCOUNT_ONE: u64 = i64::MIN as u64;
// TODO: on all number functions double check and deal with over/underflow.
@ -210,12 +210,33 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn mul_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn imul_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
) where
ASM: Assembler<GeneralReg, FloatReg>,
CC: CallConv<GeneralReg, FloatReg, ASM>;
fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
fn sub_reg64_reg64_reg64(
@ -339,6 +360,19 @@ pub fn new_backend_64bit<
}
}
macro_rules! quadword_and_smaller {
() => {
IntWidth::I64
| IntWidth::U64
| IntWidth::I32
| IntWidth::U32
| IntWidth::I16
| IntWidth::U16
| IntWidth::I8
| IntWidth::U8
};
}
impl<
'a,
GeneralReg: RegTrait,
@ -699,16 +733,7 @@ impl<
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
match layout {
Layout::Builtin(Builtin::Int(
IntWidth::I64
| IntWidth::U64
| IntWidth::I32
| IntWidth::U32
| IntWidth::I16
| IntWidth::U16
| IntWidth::I8
| IntWidth::U8,
)) => {
Layout::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -736,7 +761,9 @@ impl<
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
match layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
Layout::Builtin(Builtin::Int(
IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -746,6 +773,37 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
Layout::Builtin(Builtin::Int(
IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::umul_reg64_reg64_reg64(
&mut self.buf,
&mut self.storage_manager,
dst_reg,
src1_reg,
src2_reg,
);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::mul_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::mul_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumMul: layout, {:?}", x),
}
}

View file

@ -22,7 +22,7 @@ use StackStorage::*;
use Storage::*;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum RegStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
pub enum RegStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
General(GeneralReg),
Float(FloatReg),
}
@ -756,7 +756,7 @@ impl<
#[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free.
fn ensure_reg_free(
pub fn ensure_reg_free(
&mut self,
buf: &mut Vec<'a, u8>,
wanted_reg: RegStorage<GeneralReg, FloatReg>,

View file

@ -1009,6 +1009,58 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
imul_reg64_reg64(buf, dst, src2);
}
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) where
ASM: Assembler<X86_64GeneralReg, X86_64FloatReg>,
CC: CallConv<X86_64GeneralReg, X86_64FloatReg, ASM>,
{
use crate::generic64::RegStorage;
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RAX));
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RDX));
mov_reg64_reg64(buf, X86_64GeneralReg::RAX, src1);
mul_reg64_reg64(buf, src2);
mov_reg64_reg64(buf, dst, X86_64GeneralReg::RAX);
}
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
mulss_freg32_freg32(buf, dst, src2);
} else if dst == src2 {
mulss_freg32_freg32(buf, dst, src1);
} else {
movss_freg32_freg32(buf, dst, src1);
mulss_freg32_freg32(buf, dst, src2);
}
}
#[inline(always)]
fn mul_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
mulsd_freg64_freg64(buf, dst, src2);
} else if dst == src2 {
mulsd_freg64_freg64(buf, dst, src1);
} else {
movsd_freg64_freg64(buf, dst, src1);
mulsd_freg64_freg64(buf, dst, src2);
}
}
#[inline(always)]
fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize {
jmp_imm32(buf, offset);
@ -1280,12 +1332,25 @@ impl X86_64Assembler {
}
}
const REX: u8 = 0x40;
const REX_W: u8 = REX | 0x8;
// see https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding
/// If set, 64-bit operand size is used
const REX_PREFIX_W: u8 = 0b1000;
/// Extension to the MODRM.reg
const REX_PREFIX_R: u8 = 0b0100;
#[allow(unused)]
/// Extension to the SIB.index field
const REX_PREFIX_X: u8 = 0b0010;
/// Extension to the MODRM.rm
const REX_PREFIX_B: u8 = 0b0001;
/// Wide REX
const REX_W: u8 = REX | REX_PREFIX_W;
#[inline(always)]
fn add_rm_extension<T: RegTrait>(reg: T, byte: u8) -> u8 {
if reg.value() > 7 {
byte | 1
byte | REX_PREFIX_B
} else {
byte
}
@ -1299,7 +1364,7 @@ fn add_opcode_extension(reg: X86_64GeneralReg, byte: u8) -> u8 {
#[inline(always)]
fn add_reg_extension<T: RegTrait>(reg: T, byte: u8) -> u8 {
if reg.value() > 7 {
byte | 4
byte | REX_PREFIX_R
} else {
byte
}
@ -1396,6 +1461,46 @@ fn addss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64Fl
}
}
/// `MULSD xmm1,xmm2/m64` -> Multiply the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF2,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF2, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// `ADDSS xmm1,xmm2/m64` -> Add the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF3,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF3, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
#[inline(always)]
fn andpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
@ -1465,6 +1570,19 @@ fn imul_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gen
extended_binop_reg64_reg64(0x0F, 0xAF, buf, src, dst);
}
/// `MUL r/m64` -> Unsigned Multiply r/m64 to r64.
#[inline(always)]
fn mul_reg64_reg64(buf: &mut Vec<'_, u8>, src: X86_64GeneralReg) {
let mut rex = REX_W;
rex = add_reg_extension(src, rex);
if src.value() > 7 {
rex |= REX_PREFIX_B;
}
buf.extend(&[rex, 0xF7, 0b1110_0000 | (src as u8 % 8)]);
}
/// Jump near, relative, RIP = RIP + 32-bit displacement sign extended to 64-bits.
#[inline(always)]
fn jmp_imm32(buf: &mut Vec<'_, u8>, imm: i32) {
@ -2086,6 +2204,35 @@ mod tests {
);
}
#[test]
fn test_mul_reg64_reg64() {
disassembler_test!(
mul_reg64_reg64,
|reg| format!("mul {}", reg),
ALL_GENERAL_REGS
);
}
#[test]
fn test_mulsd_freg64_freg64() {
disassembler_test!(
mulsd_freg64_freg64,
|reg1, reg2| format!("mulsd {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_mulss_freg32_freg32() {
disassembler_test!(
mulss_freg32_freg32,
|reg1, reg2| format!("mulss {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_jmp_imm32() {
const INST_SIZE: i32 = 5;

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
use bumpalo::{collections::Vec, Bump};

View file

@ -172,7 +172,7 @@ fn build_object<'a, B: Backend<'a>>(
let arena = backend.env().arena;
/*
// Commented out because we couldn't figure out how to get it to work on mac - see https://github.com/rtfeldman/roc/pull/1323
// Commented out because we couldn't figure out how to get it to work on mac - see https://github.com/roc-lang/roc/pull/1323
let comment = output.add_section(vec![], b".comment".to_vec(), SectionKind::OtherString);
output.append_section_data(
comment,

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
// we actually want to compare against the literal float bits
#![allow(clippy::float_cmp)]

View file

@ -3929,7 +3929,7 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let jmp_buf = get_sjlj_buffer(env);
if cfg!(target_arch = "aarch64") {
// Due to https://github.com/rtfeldman/roc/issues/2965, we use a setjmp we linked in from Zig
// Due to https://github.com/roc-lang/roc/issues/2965, we use a setjmp we linked in from Zig
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP)
} else {
// Anywhere else, use the LLVM intrinsic.
@ -6459,11 +6459,11 @@ fn to_cc_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
) -> BasicTypeEnum<'ctx> {
match layout {
Layout::Builtin(builtin) => to_cc_type_builtin(env, builtin),
_ => {
match layout.runtime_representation() {
Layout::Builtin(builtin) => to_cc_type_builtin(env, &builtin),
layout => {
// TODO this is almost certainly incorrect for bigger structs
basic_type_from_layout(env, layout)
basic_type_from_layout(env, &layout)
}
}
}
@ -7087,22 +7087,13 @@ fn build_int_binop<'a, 'ctx, 'env>(
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(),
NumShiftLeftBy => {
// NOTE arguments are flipped;
// we write `assert_eq!(0b0000_0001 << 0, 0b0000_0001);`
// as `Num.shiftLeftBy 0 0b0000_0001
bd.build_left_shift(rhs, lhs, "int_shift_left").into()
}
NumShiftRightBy => {
// NOTE arguments are flipped;
bd.build_right_shift(rhs, lhs, true, "int_shift_right")
.into()
}
NumShiftRightZfBy => {
// NOTE arguments are flipped;
bd.build_right_shift(rhs, lhs, false, "int_shift_right_zf")
.into()
}
NumShiftLeftBy => bd.build_left_shift(lhs, rhs, "int_shift_left").into(),
NumShiftRightBy => bd
.build_right_shift(lhs, rhs, true, "int_shift_right")
.into(),
NumShiftRightZfBy => bd
.build_right_shift(lhs, rhs, false, "int_shift_right_zf")
.into(),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);

View file

@ -361,7 +361,7 @@ impl<'ctx> RocUnion<'ctx> {
// set the tag id
//
// NOTE: setting the tag id initially happened before writing the data into it.
// That turned out to expose UB. More info at https://github.com/rtfeldman/roc/issues/3554
// That turned out to expose UB. More info at https://github.com/roc-lang/roc/issues/3554
if let Some(tag_id) = tag_id {
let tag_id_type = match self.tag_type.unwrap() {
TagType::I8 => env.context.i8_type(),

View file

@ -517,7 +517,7 @@ impl<'a> WasmBackend<'a> {
// Load all the arguments for the inner function
for (i, wrapper_arg) in wrapper_arg_layouts.iter().enumerate() {
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner)
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner). We'll handle it below.
let is_return_pointer = i == wrapper_arg_layouts.len() - 1; // Skip return pointer (may not be an arg for inner. And if it is, swaps from end to start)
if is_closure_data || is_return_pointer {
continue;
@ -540,7 +540,23 @@ impl<'a> WasmBackend<'a> {
// If the inner function has closure data, it's the last arg of the inner fn
let closure_data_layout = wrapper_arg_layouts[0];
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
// The closure data exists, and will have been passed in to the wrapper as a
// one-element struct.
let inner_closure_data_layout = match closure_data_layout {
Layout::Struct {
field_layouts: [inner],
..
} => inner,
other => internal_error!(
"Expected a boxed layout for wrapped closure data, got {:?}",
other
),
};
self.code_builder.get_local(LocalId(0));
// Since the closure data is wrapped in a one-element struct, we've been passed in the
// pointer to that struct in the stack memory. To get the closure data we just need to
// dereference the pointer.
self.dereference_boxed_value(inner_closure_data_layout);
}
// Call the wrapped inner function

View file

@ -183,7 +183,7 @@ impl<'a> LowLevelCall<'a> {
/// Wrap an integer that should have less than 32 bits, but is represented in Wasm as i32.
/// This may seem like deliberately introducing an error!
/// But we want all targets to behave the same, and hash algos rely on wrapping.
/// Discussion: https://github.com/rtfeldman/roc/pull/2117#discussion_r760723063
/// Discussion: https://github.com/roc-lang/roc/pull/2117#discussion_r760723063
fn wrap_small_int(&self, backend: &mut WasmBackend<'a>, int_width: IntWidth) {
let bits = 8 * int_width.stack_size() as i32;
let shift = 32 - bits;
@ -1599,11 +1599,11 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftLeftBy => {
// Swap order of arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
let num = self.arguments[0];
let bits = self.arguments[1];
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
match CodeGenNumType::from(self.ret_layout) {
I32 => backend.code_builder.i32_shl(),
I64 => backend.code_builder.i64_shl(),
@ -1612,8 +1612,8 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftRightBy => {
let bits = self.arguments[0];
let num = self.arguments[1];
let num = self.arguments[0];
let bits = self.arguments[1];
match CodeGenNumType::from(self.ret_layout) {
I32 => {
// In most languages this operation is for signed numbers, but Roc defines it on all integers.
@ -1657,41 +1657,39 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftRightZfBy => {
let num = self.arguments[0];
let bits = self.arguments[1];
match CodeGenNumType::from(self.ret_layout) {
I32 => {
// In most languages this operation is for unsigned numbers, but Roc defines it on all integers.
// So the argument is implicitly converted to unsigned before the shift operator.
// We need to make that conversion explicit for i8 and i16, which use Wasm's i32 type.
let bit_width = 8 * self.ret_layout.stack_size(TARGET_INFO);
if bit_width < 32 && symbol_is_signed_int(backend, self.arguments[0]) {
if bit_width < 32 && symbol_is_signed_int(backend, bits) {
let mask = (1 << bit_width) - 1;
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.arguments[1]]);
.load_symbols(&mut backend.code_builder, &[num]);
backend.code_builder.i32_const(mask);
backend.code_builder.i32_and();
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.arguments[0]]);
.load_symbols(&mut backend.code_builder, &[bits]);
} else {
// swap the arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
}
backend.code_builder.i32_shr_u();
}
I64 => {
// swap the arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
backend.code_builder.i64_shr_u();
}
I128 => todo!("{:?} for I128", self.lowlevel),
@ -1857,11 +1855,15 @@ impl<'a> LowLevelCall<'a> {
/// Equality and inequality
/// These can operate on any data type (except functions) so they're more complex than other operators.
fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) {
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]];
let arg_layout =
backend.storage.symbol_layouts[&self.arguments[0]].runtime_representation();
let other_arg_layout =
backend.storage.symbol_layouts[&self.arguments[1]].runtime_representation();
debug_assert!(
arg_layout == other_arg_layout,
"Cannot do `==` comparison on different types"
"Cannot do `==` comparison on different types: {:?} vs {:?}",
arg_layout,
other_arg_layout
);
let invert_result = matches!(self.lowlevel, LowLevel::NotEq);
@ -2113,6 +2115,18 @@ pub fn call_higher_order_lowlevel<'a>(
..
} = passed_function;
// The zig lowlevel builtins expect the passed functions' closure data to always
// be sent as an opaque pointer. On the Roc side, however, we need to call the passed function
// with the Roc representation of the closure data. There are three possible cases for that
// representation:
//
// 1. The closure data is a struct
// 2. The closure data is an unwrapped value
// 3. There is no closure data
//
// To uniformly deal with the first two cases, always put the closure data, when it exists,
// into a one-element struct. That way, we get a pointer (i32) that can be passed to the zig lowlevels.
// The wrapper around the passed function will access the actual closure data in the struct.
let (closure_data_layout, closure_data_exists) =
match backend.storage.symbol_layouts[captured_environment] {
Layout::LambdaSet(lambda_set) => {
@ -2131,6 +2145,46 @@ pub fn call_higher_order_lowlevel<'a>(
x => internal_error!("Closure data has an invalid layout\n{:?}", x),
};
let (wrapped_captured_environment, wrapped_captures_layout) = if closure_data_exists {
// If there is closure data, make sure we put in a struct it before passing it to the
// external builtin impl. That way it's always an `i32` pointer.
let wrapped_closure_data_sym = backend.create_symbol("wrapped_captures");
let wrapped_captures_layout =
Layout::struct_no_name_order(backend.env.arena.alloc([closure_data_layout]));
// make sure that the wrapping struct is available in stack memory, so we can hand out a
// pointer to it.
let wrapped_storage = backend.storage.allocate_var(
wrapped_captures_layout,
wrapped_closure_data_sym,
crate::storage::StoredVarKind::Variable,
);
let (wrapped_storage_local_ptr, wrapped_storage_offset) = match wrapped_storage {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(backend.storage.stack_frame_pointer)
}
other => internal_error!(
"Struct should be allocated in stack memory, but it's in {:?}",
other
),
};
// copy the actual closure data into the first and only element of the wrapping struct.
backend.storage.copy_value_to_memory(
&mut backend.code_builder,
wrapped_storage_local_ptr,
wrapped_storage_offset,
*captured_environment,
);
(wrapped_closure_data_sym, wrapped_captures_layout)
} else {
// If we don't capture anything, pass along the captured environment as-is - the wrapper
// function will take care not to unwrap this.
(*captured_environment, closure_data_layout)
};
// We create a wrapper around the passed function, which just unboxes the arguments.
// This allows Zig builtins to have a generic pointer-based interface.
let helper_proc_source = {
@ -2164,7 +2218,7 @@ pub fn call_higher_order_lowlevel<'a>(
argument_layouts.len()
};
wrapper_arg_layouts.push(closure_data_layout);
wrapper_arg_layouts.push(wrapped_captures_layout);
wrapper_arg_layouts.extend(
argument_layouts
.iter()
@ -2204,7 +2258,7 @@ pub fn call_higher_order_lowlevel<'a>(
.get_refcount_fn_index(Layout::Builtin(Builtin::Int(IntWidth::I32)), HelperOp::Inc);
backend.get_fn_ptr(inc_fn)
} else {
let inc_fn = backend.get_refcount_fn_index(closure_data_layout, HelperOp::Inc);
let inc_fn = backend.get_refcount_fn_index(wrapped_captures_layout, HelperOp::Inc);
backend.get_fn_ptr(inc_fn)
};
@ -2218,7 +2272,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2231,7 +2285,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2244,7 +2298,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2257,7 +2311,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2280,7 +2334,9 @@ pub fn call_higher_order_lowlevel<'a>(
backend.storage.load_symbol_zig(cb, *xs);
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[*captured_environment]);
backend
.storage
.load_symbols(cb, &[wrapped_captured_environment]);
} else {
// load_symbols assumes that a zero-size arg should be eliminated in code gen,
// but that's a specialization that our Zig code doesn't have! Pass a null pointer.

View file

@ -719,9 +719,10 @@ impl<'a> Storage<'a> {
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
// Note: load_symbols will not destroy the value, so we can use it again later.
// It will leave a Popped marker in the VM stack model in CodeBuilder
self.load_symbols(code_builder, &[from_symbol]);
code_builder.set_local(*to_local_id);
self.symbol_storage_map.insert(from_symbol, to.clone());
}
(

View file

@ -234,6 +234,14 @@ impl Wasm32Result for () {
}
}
impl Wasm32Result for std::convert::Infallible {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
code_builder.call(main_function_index, 0, false);
code_builder.get_global(0);
code_builder.build_fn_header_and_footer(&[], 0, None);
}
}
impl<T, U> Wasm32Result for (T, U)
where
T: Wasm32Result + Wasm32Sized,

View file

@ -203,6 +203,30 @@ impl From<&str> for IdentStr {
}
}
impl From<IdentStr> for String {
fn from(ident_str: IdentStr) -> Self {
if ident_str.is_small_str() {
// Copy it to a heap allocation
ident_str.as_str().to_string()
} else {
// Reuse the existing heap allocation
let string = unsafe {
String::from_raw_parts(
ident_str.as_ptr() as *mut u8,
ident_str.len(),
ident_str.len(),
)
};
// Make sure not to drop the IdentStr, since now there's
// a String referencing its heap-allocated contents.
std::mem::forget(ident_str);
string
}
}
}
impl From<String> for IdentStr {
fn from(string: String) -> Self {
if string.len() <= Self::SMALL_STR_BYTES {

View file

@ -358,7 +358,7 @@ pub fn unify(
);
// 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.
// After we land https://github.com/rtfeldman/roc/issues/3207 this concern should totally
// After we land https://github.com/roc-lang/roc/issues/3207 this concern should totally
// go away.
let _ = must_implement_constraints;
// Pools are only used to keep track of variable ranks for generalization purposes.

View file

@ -54,6 +54,29 @@ pub fn load_single_threaded<'a>(
)
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum LoadMonomorphizedError<'a> {
LoadingProblem(LoadingProblem<'a>),
/// Errors in the module that should be reported, without compiling the executable.
/// Relevant in check-and-then-build mode.
ErrorModule(LoadedModule),
}
impl<'a> From<LoadingProblem<'a>> for LoadMonomorphizedError<'a> {
fn from(problem: LoadingProblem<'a>) -> Self {
Self::LoadingProblem(problem)
}
}
// HACK only relevant because of some uses of `map_err` that decay into this error, but call `todo` -
// rustc seems to be unhappy with that.
impl<'a> From<()> for LoadMonomorphizedError<'a> {
fn from(_: ()) -> Self {
todo!()
}
}
#[allow(clippy::too_many_arguments)]
pub fn load_and_monomorphize_from_str<'a>(
arena: &'a Bump,
@ -78,14 +101,14 @@ pub fn load_and_monomorphize(
filename: PathBuf,
exposed_types: ExposedByModule,
load_config: LoadConfig,
) -> Result<MonomorphizedModule<'_>, LoadingProblem<'_>> {
) -> Result<MonomorphizedModule<'_>, LoadMonomorphizedError<'_>> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, load_config.render)?;
match load(arena, load_start, exposed_types, load_config)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
TypeChecked(module) => Err(LoadMonomorphizedError::ErrorModule(module)),
}
}

View file

@ -29,10 +29,10 @@ roc_debug_flags = { path = "../debug_flags" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] }
parking_lot = "0.12"
crossbeam = "0.8.1"
crossbeam = "0.8.2"
[dev-dependencies]
pretty_assertions = "1.0.0"
maplit = "1.0.2"
indoc = "1.0.3"
indoc = "1.0.7"
roc_test_utils = { path = "../../test_utils" }

View file

@ -130,13 +130,15 @@ pub enum ExecutionMode {
Test,
Check,
Executable,
/// Like [`ExecutionMode::Executable`], but stops in the presence of type errors.
ExecutableIfCheck,
}
impl ExecutionMode {
fn goal_phase(&self) -> Phase {
match self {
ExecutionMode::Test | ExecutionMode::Executable => Phase::MakeSpecializations,
ExecutionMode::Check => Phase::SolveTypes,
ExecutionMode::Check | ExecutionMode::ExecutableIfCheck => Phase::SolveTypes,
}
}
}
@ -168,6 +170,22 @@ struct ModuleCache<'a> {
sources: MutMap<ModuleId, (PathBuf, &'a str)>,
}
impl<'a> ModuleCache<'a> {
pub fn total_problems(&self) -> usize {
let mut total = 0;
for problems in self.can_problems.values() {
total += problems.len();
}
for problems in self.type_problems.values() {
total += problems.len();
}
total
}
}
impl Default for ModuleCache<'_> {
fn default() -> Self {
let mut module_names = MutMap::default();
@ -2385,12 +2403,35 @@ fn update<'a>(
.extend(solved_module.aliases.keys().copied());
}
if is_host_exposed && state.goal_phase() == Phase::SolveTypes {
let finish_type_checking = is_host_exposed &&
(state.goal_phase() == Phase::SolveTypes)
// If we're running in check-and-then-build mode, only exit now there are errors.
&& (!matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) || state.module_cache.total_problems() > 0);
if finish_type_checking {
debug_assert!(work.is_empty());
debug_assert!(state.dependencies.solved_all());
state.timings.insert(module_id, module_timing);
if matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) {
// We there may outstanding modules in the typecheked cache whose ident IDs
// aren't registered; transfer all of their idents over to the state, since
// we're now done and ready to report errors.
for (
module_id,
TypeCheckedModule {
ident_ids,
module_timing,
..
},
) in state.module_cache.typechecked.drain()
{
state.constrained_ident_ids.insert(module_id, ident_ids);
state.timings.insert(module_id, module_timing);
}
}
let documentation = {
let mut empty = MutMap::default();
std::mem::swap(&mut empty, &mut state.module_cache.documentation);
@ -2427,7 +2468,9 @@ fn update<'a>(
},
);
if state.goal_phase() > Phase::SolveTypes {
if state.goal_phase() > Phase::SolveTypes
|| matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
{
let layout_cache = state
.layout_caches
.pop()
@ -2452,6 +2495,25 @@ fn update<'a>(
state.timings.insert(module_id, module_timing);
}
let work = if is_host_exposed
&& matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
{
debug_assert!(
work.is_empty(),
"work left over after host exposed is checked"
);
// Update the goal phase to target full codegen.
state.exec_mode = ExecutionMode::Executable;
// Load the find + make specializations portion of the dependency graph.
state
.dependencies
.load_find_and_make_specializations_after_check()
} else {
work
};
start_tasks(arena, &mut state, work, injector, worker_listeners)?;
}
@ -2810,7 +2872,7 @@ fn finish_specialization(
let entry_point = {
match exec_mode {
ExecutionMode::Test => EntryPoint::Test,
ExecutionMode::Executable => {
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
let path_to_platform = {
use PlatformPath::*;
let package_name = match platform_path {
@ -5007,7 +5069,9 @@ fn build_pending_specializations<'a>(
// skip expectations if we're not going to run them
match execution_mode {
ExecutionMode::Test => { /* fall through */ }
ExecutionMode::Check | ExecutionMode::Executable => continue,
ExecutionMode::Check
| ExecutionMode::Executable
| ExecutionMode::ExecutableIfCheck => continue,
}
// mark this symbol as a top-level thunk before any other work on the procs
@ -5081,7 +5145,9 @@ fn build_pending_specializations<'a>(
// skip expectations if we're not going to run them
match execution_mode {
ExecutionMode::Test => { /* fall through */ }
ExecutionMode::Check | ExecutionMode::Executable => continue,
ExecutionMode::Check
| ExecutionMode::Executable
| ExecutionMode::ExecutableIfCheck => continue,
}
// mark this symbol as a top-level thunk before any other work on the procs

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod docs;
pub mod file;

View file

@ -166,11 +166,10 @@ impl<'a> Dependencies<'a> {
}
}
if goal_phase >= MakeSpecializations {
// Add make specialization dependents
self.make_specializations_dependents
.add_succ(module_id, dependencies.iter().map(|dep| *dep.as_inner()));
}
// Add "make specialization" dependents. Even if we're not targeting making
// specializations right now, we may re-enter to do so later.
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
@ -374,6 +373,80 @@ impl<'a> Dependencies<'a> {
}
}
/// Loads the dependency graph to find and make specializations, and returns the next jobs to
/// be run.
///
/// This should be used when the compiler wants to build or run a Roc executable if and only if
/// previous stages succeed; in such cases we load the dependency graph dynamically.
pub fn load_find_and_make_specializations_after_check(&mut self) -> MutSet<(ModuleId, Phase)> {
let mut output = MutSet::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, info) in make_specializations_dependents.0.iter_mut() {
debug_assert!(self.status.get_mut(&Job::Step(module, Phase::FindSpecializations)).is_none(), "should only have targeted solving types, but there is already a goal to find specializations");
debug_assert!(self.status.get_mut(&Job::Step(module, Phase::MakeSpecializations)).is_none(), "should only have targeted solving types, but there is already a goal to make specializations");
debug_assert!(
module == ModuleId::DERIVED_GEN || info.succ.contains(&ModuleId::DERIVED_GEN),
"derived module not accounted for in {:?}",
(module, info)
);
let mut has_find_specialization_dep = false;
for &module_dep in info.succ.iter() {
// The modules in `succ` are the modules for which specializations should be made
// after the current one. But, their specializations should be found before the
// current one.
if module_dep != ModuleId::DERIVED_GEN {
// We never find specializations for DERIVED_GEN
self.add_dependency(module, module_dep, Phase::FindSpecializations);
has_find_specialization_dep = true;
}
self.add_dependency(module_dep, module, Phase::MakeSpecializations);
self.add_dependency(ModuleId::DERIVED_GEN, module, Phase::MakeSpecializations);
// `module_dep` can't make its specializations until the current module does.
info.has_pred = true;
}
if module != ModuleId::DERIVED_GEN {
self.add_to_status_for_phase(module, Phase::FindSpecializations);
self.add_dependency_help(
module,
module,
Phase::MakeSpecializations,
Phase::FindSpecializations,
);
}
self.add_to_status_for_phase(module, Phase::MakeSpecializations);
if !has_find_specialization_dep && module != ModuleId::DERIVED_GEN {
// We don't depend on any other modules having their specializations found first,
// so start finding specializations from this module.
output.insert((module, Phase::FindSpecializations));
}
}
std::mem::swap(
&mut self.make_specializations_dependents,
&mut make_specializations_dependents,
);
debug_assert_eq!(
make_specializations_dependents.0.len(),
default_make_specializations_dependents_len,
"more modules were added to the graph: {:?}",
make_specializations_dependents
);
output
}
/// Load the entire "make specializations" dependency graph and start from the top.
pub fn reload_make_specialization_pass(&mut self) -> MutSet<(ModuleId, Phase)> {
let mut output = MutSet::default();

View file

@ -699,7 +699,7 @@ fn platform_parse_error() {
}
#[test]
// See https://github.com/rtfeldman/roc/issues/2413
// See https://github.com/roc-lang/roc/issues/2413
fn platform_exposes_main_return_by_pointer_issue() {
let modules = vec![
(

View file

@ -65,6 +65,12 @@ impl TagName {
}
}
impl From<&str> for TagName {
fn from(string: &str) -> Self {
Self(string.into())
}
}
impl ModuleName {
// NOTE: After adding one of these, go to `impl ModuleId` and
// add a corresponding ModuleId to there!
@ -187,6 +193,20 @@ impl Lowercase {
}
}
impl From<Lowercase> for String {
fn from(lowercase: Lowercase) -> Self {
lowercase.0.into()
}
}
impl From<Lowercase> for Box<str> {
fn from(lowercase: Lowercase) -> Self {
let string: String = lowercase.0.into();
string.into()
}
}
impl<'a> From<&'a str> for Lowercase {
fn from(string: &'a str) -> Self {
Self(string.into())

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod called_via;

View file

@ -152,6 +152,7 @@ impl Symbol {
}
pub const fn to_ne_bytes(self) -> [u8; 8] {
// repr(packed(4)) is repr(c), and with the fields as defined will not having padding.
unsafe { std::mem::transmute(self) }
}
@ -1416,10 +1417,11 @@ define_builtins! {
19 DECODE_BOOL: "bool"
20 DECODE_STRING: "string"
21 DECODE_LIST: "list"
22 DECODE_CUSTOM: "custom"
23 DECODE_DECODE_WITH: "decodeWith"
24 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial"
25 DECODE_FROM_BYTES: "fromBytes"
22 DECODE_RECORD: "record"
23 DECODE_CUSTOM: "custom"
24 DECODE_DECODE_WITH: "decodeWith"
25 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial"
26 DECODE_FROM_BYTES: "fromBytes"
}
13 JSON: "Json" => {
0 JSON_JSON: "Json"

View file

@ -23,5 +23,5 @@ roc_error_macros = {path="../../error_macros"}
roc_debug_flags = {path="../debug_flags"}
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.12.1", features = [ "bumpalo" ] }
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
static_assertions = "1.1.0"

View file

@ -1,8 +1,9 @@
#![allow(clippy::manual_map)]
use crate::layout::{
Builtin, CapturesNiche, ClosureRepresentation, LambdaName, LambdaSet, Layout, LayoutCache,
LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant,
Builtin, CapturesNiche, ClosureCallOptions, ClosureRepresentation, EnumDispatch, LambdaName,
LambdaSet, Layout, LayoutCache, LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout,
WrappedVariant,
};
use bumpalo::collections::{CollectIn, Vec};
use bumpalo::Bump;
@ -343,10 +344,16 @@ impl<'a> Proc<'a> {
D::Doc: Clone,
A: Clone,
{
let args_doc = self
.args
.iter()
.map(|(_, symbol)| symbol_to_doc(alloc, *symbol));
let args_doc = self.args.iter().map(|(layout, symbol)| {
let arg_doc = symbol_to_doc(alloc, *symbol);
if pretty_print_ir_symbols() {
arg_doc
.append(alloc.reflow(": "))
.append(layout.to_doc(alloc, Parens::NotNeeded))
} else {
arg_doc
}
});
if pretty_print_ir_symbols() {
alloc
@ -621,6 +628,10 @@ impl<'a> Suspended<'a> {
}
}
fn is_empty(&self) -> bool {
self.symbol_or_lambdas.is_empty()
}
fn specialization(
&mut self,
subs: &mut Subs,
@ -654,8 +665,22 @@ enum PendingSpecializations<'a> {
/// that we can give specializations we need to modules higher up in the dependency chain, so
/// that they can start making specializations too
Finding(Suspended<'a>),
/// We are making specializations. If any new one comes up, we can just make it immediately
Making,
/// We are making specializations.
/// If any new one comes up while specializing a body, we can do one of two things:
/// - if the new specialization is for a symbol that is not in the current stack of symbols
/// being specialized, make it immediately
/// - if it is, we must suspend the specialization, and we'll do it once the stack is clear
/// again.
Making(Suspended<'a>),
}
impl<'a> PendingSpecializations<'a> {
fn is_empty(&self) -> bool {
match self {
PendingSpecializations::Finding(suspended)
| PendingSpecializations::Making(suspended) => suspended.is_empty(),
}
}
}
#[derive(Clone, Debug, Default)]
@ -940,6 +965,8 @@ pub struct Procs<'a> {
pub runtime_errors: BumpMap<Symbol, &'a str>,
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations<'a>>,
symbol_specializations: SymbolSpecializations<'a>,
/// The current set of functions under specialization.
pub specialization_stack: Vec<'a, Symbol>,
}
impl<'a> Procs<'a> {
@ -954,8 +981,43 @@ impl<'a> Procs<'a> {
runtime_errors: BumpMap::new_in(arena),
externals_we_need: BumpMap::new_in(arena),
symbol_specializations: Default::default(),
specialization_stack: Vec::with_capacity_in(16, arena),
}
}
fn push_active_specialization(&mut self, specialization: Symbol) {
self.specialization_stack.push(specialization);
}
fn pop_active_specialization(&mut self, specialization: Symbol) {
let popped = self
.specialization_stack
.pop()
.expect("specialization stack is empty");
debug_assert_eq!(
popped, specialization,
"incorrect popped specialization: passed {:?}, but was {:?}",
specialization, popped
);
}
/// If we need to specialize a function that is already in the stack, we must wait to do so
/// until that function is popped off. That's because the type environment will be configured
/// for the existing specialization on the stack.
///
/// For example, in
///
/// foo = \val, b -> if b then "done" else bar val
/// bar = \_ -> foo {} True
/// foo "" False
///
/// During the specialization of `foo : Str False -> Str`, we specialize `bar : Str -> Str`,
/// which in turn needs a specialization of `foo : {} False -> Str`. However, we can't
/// specialize both `foo : Str False -> Str` and `foo : {} False -> Str` at the same time, so
/// the latter specialization must be deferred.
fn symbol_needs_suspended_specialization(&self, specialization: Symbol) -> bool {
self.specialization_stack.contains(&specialization)
}
}
#[derive(Clone, Debug, PartialEq)]
@ -1045,8 +1107,14 @@ impl<'a> Procs<'a> {
debug_assert!(layout.arguments.is_empty());
}
match &mut self.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization =
self.symbol_needs_suspended_specialization(name.name());
match (
&mut self.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
// register the pending specialization, so this gets code genned later
suspended.specialization(env.subs, name, layout, annotation);
@ -1080,7 +1148,7 @@ impl<'a> Procs<'a> {
}
}
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
// Mark this proc as in-progress, so if we're dealing with
// mutually recursive functions, we don't loop forever.
// (We had a bug around this before this system existed!)
@ -1178,7 +1246,8 @@ impl<'a> Procs<'a> {
}
// If this is an imported symbol, let its home module make this specialization
if env.is_imported_symbol(name.name()) {
if env.is_imported_symbol(name.name()) || env.is_unloaded_derived_symbol(name.name(), self)
{
add_needed_external(self, env, fn_var, name);
return;
}
@ -1190,11 +1259,17 @@ impl<'a> Procs<'a> {
// This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization =
self.symbol_needs_suspended_specialization(name.name());
match (
&mut self.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
suspended.specialization(env.subs, name, layout, fn_var);
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
let symbol = name;
let partial_proc_id = match self.partial_procs.symbol_to_id(symbol.name()) {
@ -1207,7 +1282,7 @@ impl<'a> Procs<'a> {
// (We had a bug around this before this system existed!)
self.specialized.mark_in_progress(symbol.name(), layout);
// See https://github.com/rtfeldman/roc/issues/1600
// See https://github.com/roc-lang/roc/issues/1600
//
// The annotation variable is the generic/lifted/top-level annotation.
// It is connected to the variables of the function's body
@ -1342,6 +1417,13 @@ impl<'a, 'i> Env<'a, 'i> {
self.home == ModuleId::DERIVED_GEN
&& symbol.module_id() == ModuleId::DERIVED_SYNTH
&& !procs.partial_procs.contains_key(symbol)
// TODO: locking to find the answer in the `Derived_gen` module is not great, since
// Derived_gen also blocks other modules specializing. Improve this later.
&& self
.derived_module
.lock()
.expect("derived module is poisoned")
.is_derived_def(symbol)
}
/// Unifies two variables and performs lambda set compaction.
@ -2501,7 +2583,7 @@ fn patterns_to_when<'a>(
// are only stores anyway, no branches.
//
// NOTE this fails if the pattern contains rigid variables,
// see https://github.com/rtfeldman/roc/issues/786
// see https://github.com/roc-lang/roc/issues/786
// this must be fixed when moving exhaustiveness checking to the new canonical AST
for (pattern_var, annotated_mark, pattern) in patterns.into_iter() {
if annotated_mark.exhaustive.is_non_exhaustive(env.subs) {
@ -2739,26 +2821,51 @@ pub fn specialize_all<'a>(
specializations_for_host: HostSpecializations<'a>,
layout_cache: &mut LayoutCache<'a>,
) -> Procs<'a> {
for externals in externals_others_need {
specialize_external_specializations(env, &mut procs, layout_cache, externals);
}
// When calling from_can, pending_specializations should be unavailable.
// This must be a single pass, and we must not add any more entries to it!
let pending_specializations = std::mem::replace(
&mut procs.pending_specializations,
PendingSpecializations::Making,
PendingSpecializations::Making(Suspended::new_in(env.arena)),
);
// Add all of our existing pending specializations.
match pending_specializations {
PendingSpecializations::Making => {}
PendingSpecializations::Finding(suspended) => {
specialize_suspended(env, &mut procs, layout_cache, suspended)
}
PendingSpecializations::Making(suspended) => {
debug_assert!(
suspended.is_empty(),
"suspended specializations cannot ever start off non-empty when making"
);
}
}
// Specialize all the symbols everyone else needs.
for externals in externals_others_need {
specialize_external_specializations(env, &mut procs, layout_cache, externals);
}
// Specialize any symbols the host needs.
specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host);
// Now, we must go through and continuously complete any new suspended specializations that were
// discovered in specializing the other demanded symbols.
while !procs.pending_specializations.is_empty() {
let pending_specializations = std::mem::replace(
&mut procs.pending_specializations,
PendingSpecializations::Making(Suspended::new_in(env.arena)),
);
match pending_specializations {
PendingSpecializations::Making(suspended) => {
specialize_suspended(env, &mut procs, layout_cache, suspended);
}
PendingSpecializations::Finding(_) => {
internal_error!("should not have this variant after making specializations")
}
}
}
debug_assert!(
procs.symbol_specializations.is_empty(),
"{:?}",
@ -3127,6 +3234,8 @@ fn specialize_external<'a>(
closure: opt_closure_layout,
ret_layout,
} => {
let mut proc_args = Vec::from_iter_in(proc_args.iter().copied(), env.arena);
// unpack the closure symbols, if any
match (opt_closure_layout, captured_symbols) {
(Some(closure_layout), CapturedSymbols::Captured(captured)) => {
@ -3196,6 +3305,9 @@ fn specialize_external<'a>(
ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => {
// captured variables are in symbol-alphabetic order, but now we want
// them ordered by their alignment requirements
//
// TODO: sort only the fields and apply the found permutation to the symbols
// TODO: can we move this ordering to `layout_for_member`?
let mut combined = Vec::from_iter_in(
captured.iter().map(|(x, _)| x).zip(field_layouts.iter()),
env.arena,
@ -3210,19 +3322,25 @@ fn specialize_external<'a>(
size2.cmp(&size1)
});
let ordered_field_layouts = Vec::from_iter_in(
combined.iter().map(|(_, layout)| **layout),
env.arena,
);
let ordered_field_layouts = ordered_field_layouts.into_bump_slice();
debug_assert_eq!(
captured.len(),
field_layouts.len(),
ordered_field_layouts.len(),
"{:?} captures {:?} but has layout {:?}",
lambda_name,
&captured,
&field_layouts
&ordered_field_layouts
);
for (index, (symbol, layout)) in combined.iter().enumerate() {
let expr = Expr::StructAtIndex {
index: index as _,
field_layouts,
field_layouts: ordered_field_layouts,
structure: Symbol::ARG_CLOSURE,
};
@ -3235,51 +3353,37 @@ fn specialize_external<'a>(
env.arena.alloc(specialized_body),
);
}
// let symbol = captured[0].0;
//
// substitute_in_exprs(
// env.arena,
// &mut specialized_body,
// symbol,
// Symbol::ARG_CLOSURE,
// );
}
ClosureRepresentation::Other(layout) => match layout {
Layout::Builtin(Builtin::Bool) => {
// just ignore this value
// IDEA don't pass this value in the future
}
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
// just ignore this value
// IDEA don't pass this value in the future
}
other => {
// NOTE other values always should be wrapped in a 1-element record
unreachable!(
"{:?} is not a valid closure data representation",
other
)
}
},
ClosureRepresentation::UnwrappedCapture(_layout) => {
debug_assert_eq!(captured.len(), 1);
let (captured_symbol, _captured_layout) = captured[0];
// The capture set is unwrapped, so simply replace the closure argument
// to the function with the unwrapped capture name.
let captured_symbol = get_specialized_name(captured_symbol);
let closure_arg = proc_args.last_mut().unwrap();
debug_assert_eq!(closure_arg.1, Symbol::ARG_CLOSURE);
closure_arg.1 = captured_symbol;
}
ClosureRepresentation::EnumDispatch(_) => {
// just ignore this value, since it's not a capture
// IDEA don't pass this value in the future
}
}
}
(None, CapturedSymbols::None) | (None, CapturedSymbols::Captured([])) => {}
_ => unreachable!("to closure or not to closure?"),
}
let proc_args: Vec<_> = proc_args
.iter()
.map(|&(layout, symbol)| {
// Grab the specialization symbol, if it exists.
let symbol = procs
.symbol_specializations
.remove_single(symbol)
.unwrap_or(symbol);
(layout, symbol)
})
.collect_in(env.arena);
proc_args.iter_mut().for_each(|(_layout, symbol)| {
// Grab the specialization symbol, if it exists.
*symbol = procs
.symbol_specializations
.remove_single(*symbol)
.unwrap_or(*symbol);
});
// reset subs, so we don't get type errors when specializing for a different signature
layout_cache.rollback_to(cache_snapshot);
@ -3566,6 +3670,8 @@ where
let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation;
instantiate_rigids(env.subs, annotation_var);
procs.push_active_specialization(proc_name.name());
let specialized = specialize_external(
env,
procs,
@ -3576,6 +3682,8 @@ where
partial_proc_id,
);
procs.pop_active_specialization(proc_name.name());
match specialized {
Ok(proc) => {
// when successful, the layout after unification should be the layout before unification
@ -3869,7 +3977,7 @@ pub fn with_hole<'a>(
)
}
Tag {
variant_var,
tag_union_var: variant_var,
name: tag_name,
arguments: args,
..
@ -5396,29 +5504,42 @@ where
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
ClosureRepresentation::Other(Layout::Builtin(Builtin::Bool)) => {
debug_assert_eq!(symbols.len(), 0);
ClosureRepresentation::UnwrappedCapture(_layout) => {
debug_assert_eq!(symbols.len(), 1);
debug_assert_eq!(lambda_set.set.len(), 2);
let tag_id = name.name() != lambda_set.iter_set().next().unwrap().name();
let expr = Expr::Literal(Literal::Bool(tag_id));
let mut symbols = symbols;
let (captured_symbol, _) = symbols.next().unwrap();
Stmt::Let(assigned, expr, lambda_set_layout, hole)
// The capture set is unwrapped, so just replaced the assigned capture symbol with the
// only capture.
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *captured_symbol);
hole
}
ClosureRepresentation::Other(Layout::Builtin(Builtin::Int(IntWidth::U8))) => {
debug_assert_eq!(symbols.len(), 0);
ClosureRepresentation::EnumDispatch(repr) => match repr {
EnumDispatch::Bool => {
debug_assert_eq!(symbols.len(), 0);
debug_assert!(lambda_set.set.len() > 2);
let tag_id = lambda_set
.iter_set()
.position(|s| s.name() == name.name())
.unwrap() as u8;
debug_assert_eq!(lambda_set.len(), 2);
let tag_id = name.name() != lambda_set.iter_set().next().unwrap().name();
let expr = Expr::Literal(Literal::Bool(tag_id));
let expr = Expr::Literal(Literal::Byte(tag_id));
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
EnumDispatch::U8 => {
debug_assert_eq!(symbols.len(), 0);
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
_ => unreachable!(),
debug_assert!(lambda_set.len() > 2);
let tag_id = lambda_set
.iter_set()
.position(|s| s.name() == name.name())
.unwrap() as u8;
let expr = Expr::Literal(Literal::Byte(tag_id));
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
},
};
result
@ -5692,7 +5813,7 @@ fn tag_union_to_function<'a>(
}
let loc_body = Loc::at_zero(roc_can::expr::Expr::Tag {
variant_var: return_variable,
tag_union_var: return_variable,
name: tag_name,
arguments: loc_expr_args,
ext_var,
@ -5857,10 +5978,7 @@ fn register_capturing_closure<'a>(
Content::Structure(FlatType::Func(_, closure_var, _)) => {
match LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info) {
Ok(lambda_set) => {
if let Layout::Struct {
field_layouts: &[], ..
} = lambda_set.runtime_representation()
{
if lambda_set.is_represented().is_none() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
@ -5869,7 +5987,7 @@ fn register_capturing_closure<'a>(
}
}
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
// just allow this. see https://github.com/roc-lang/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
@ -7162,7 +7280,7 @@ fn can_reuse_symbol<'a>(
if arguments.contains(&symbol) {
Value(symbol)
} else if env.is_imported_symbol(symbol) {
} else if env.is_imported_symbol(symbol) || env.is_unloaded_derived_symbol(symbol, procs) {
Imported(symbol)
} else if procs.partial_procs.contains_key(symbol) {
LocalFunction(symbol)
@ -7334,7 +7452,10 @@ fn specialize_symbol<'a>(
match procs.get_partial_proc(original) {
None => {
match arg_var {
Some(arg_var) if env.is_imported_symbol(original) => {
Some(arg_var)
if env.is_imported_symbol(original)
|| env.is_unloaded_derived_symbol(original, procs) =>
{
let raw = match layout_cache.raw_from_var(env.arena, arg_var, env.subs) {
Ok(v) => v,
Err(e) => return_on_layout_error_help!(env, e, "specialize_symbol"),
@ -7603,7 +7724,7 @@ fn build_call<'a>(
Stmt::Let(assigned, Expr::Call(call), return_layout, hole)
}
/// See https://github.com/rtfeldman/roc/issues/1549
/// See https://github.com/roc-lang/roc/issues/1549
///
/// What happened is that a function has a type error, but the arguments are not processed.
/// That means specializations were missing. Normally that is not a problem, but because
@ -7936,8 +8057,14 @@ fn call_by_name_help<'a>(
debug_assert!(top_level_layout.arguments.is_empty());
}
match &mut procs.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization =
procs.symbol_needs_suspended_specialization(proc_name.name());
match (
&mut procs.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
debug_assert!(!env.is_imported_symbol(proc_name.name()));
// register the pending specialization, so this gets code genned later
@ -7966,7 +8093,7 @@ fn call_by_name_help<'a>(
hole,
)
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name.name());
let field_symbols = field_symbols.into_bump_slice();
@ -8100,8 +8227,13 @@ fn call_by_name_module_thunk<'a>(
debug_assert!(top_level_layout.arguments.is_empty());
}
match &mut procs.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization = procs.symbol_needs_suspended_specialization(proc_name);
match (
&mut procs.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
debug_assert!(!env.is_imported_symbol(proc_name));
// register the pending specialization, so this gets code genned later
@ -8114,7 +8246,7 @@ fn call_by_name_module_thunk<'a>(
force_thunk(env, proc_name, inner_layout, assigned, hole)
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
match opt_partial_proc {
@ -9166,9 +9298,9 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
match lambda_set.runtime_representation() {
Layout::VOID => empty_lambda_set_error(),
Layout::Union(union_layout) => {
match lambda_set.call_by_name_options() {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
let result = lowlevel_union_lambda_set_to_switch(
@ -9197,7 +9329,7 @@ where
env.arena.alloc(result),
)
}
Layout::Struct { .. } => match lambda_set.iter_set().next() {
ClosureCallOptions::Struct { .. } => match lambda_set.iter_set().next() {
Some(lambda_name) => {
let call_spec_id = env.next_call_specialization_id();
let update_mode = env.next_update_mode_id();
@ -9222,39 +9354,58 @@ where
hole.clone()
}
},
Layout::Builtin(Builtin::Bool) => {
let closure_tag_id_symbol = closure_data_symbol;
ClosureCallOptions::UnwrappedCapture(_) => {
let lambda_name = lambda_set
.iter_set()
.next()
.expect("no function in lambda set");
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
let call_spec_id = env.next_call_specialization_id();
let update_mode = env.next_update_mode_id();
let call = to_lowlevel_call((
lambda_name,
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
}
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
let closure_tag_id_symbol = closure_data_symbol;
call_spec_id,
update_mode,
));
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
}
other => todo!("{:?}", other),
ClosureCallOptions::EnumDispatch(repr) => match repr {
EnumDispatch::Bool => {
let closure_tag_id_symbol = closure_data_symbol;
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
}
EnumDispatch::U8 => {
let closure_tag_id_symbol = closure_data_symbol;
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
}
},
}
}
@ -9345,15 +9496,14 @@ fn match_on_lambda_set<'a>(
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
match lambda_set.runtime_representation() {
Layout::VOID => empty_lambda_set_error(),
Layout::Union(union_layout) => {
match lambda_set.call_by_name_options() {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
let result = union_lambda_set_to_switch(
env,
lambda_set,
Layout::Union(union_layout),
closure_tag_id_symbol,
union_layout.tag_id_layout(),
closure_data_symbol,
@ -9377,9 +9527,9 @@ fn match_on_lambda_set<'a>(
env.arena.alloc(result),
)
}
Layout::Struct {
ClosureCallOptions::Struct {
field_layouts,
field_order_hash,
field_order_hash: _,
} => {
let function_symbol = match lambda_set.iter_set().next() {
Some(function_symbol) => function_symbol,
@ -9403,10 +9553,6 @@ fn match_on_lambda_set<'a>(
_ => ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
closure_data_layout: Layout::Struct {
field_layouts,
field_order_hash,
},
},
};
@ -9421,15 +9567,21 @@ fn match_on_lambda_set<'a>(
hole,
)
}
Layout::Builtin(Builtin::Bool) => {
let closure_tag_id_symbol = closure_data_symbol;
ClosureCallOptions::UnwrappedCapture(_) => {
let function_symbol = lambda_set
.iter_set()
.next()
.expect("no function in lambda set");
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
let closure_info = ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
};
union_lambda_set_branch_help(
env,
function_symbol,
closure_info,
argument_symbols,
argument_layouts,
return_layout,
@ -9437,23 +9589,38 @@ fn match_on_lambda_set<'a>(
hole,
)
}
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
let closure_tag_id_symbol = closure_data_symbol;
ClosureCallOptions::EnumDispatch(repr) => match repr {
EnumDispatch::Bool => {
let closure_tag_id_symbol = closure_data_symbol;
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
closure_data_symbol,
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
other => todo!("{:?}", other),
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
EnumDispatch::U8 => {
let closure_tag_id_symbol = closure_data_symbol;
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
},
}
}
@ -9461,7 +9628,6 @@ fn match_on_lambda_set<'a>(
fn union_lambda_set_to_switch<'a>(
env: &mut Env<'a, '_>,
lambda_set: LambdaSet<'a>,
closure_layout: Layout<'a>,
closure_tag_id_symbol: Symbol,
closure_tag_id_layout: Layout<'a>,
closure_data_symbol: Symbol,
@ -9471,7 +9637,7 @@ fn union_lambda_set_to_switch<'a>(
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
if lambda_set.set.is_empty() {
if lambda_set.is_empty() {
// NOTE this can happen if there is a type error somewhere. Since the lambda set is empty,
// there is really nothing we can do here. We generate a runtime error here which allows
// code gen to proceed. We then assume that we hit another (more descriptive) error before
@ -9481,7 +9647,7 @@ fn union_lambda_set_to_switch<'a>(
let join_point_id = JoinPointId(env.unique_symbol());
let mut branches = Vec::with_capacity_in(lambda_set.set.len(), env.arena);
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
for (i, lambda_name) in lambda_set.iter_set().enumerate() {
let closure_info = if lambda_name.no_captures() {
@ -9490,7 +9656,6 @@ fn union_lambda_set_to_switch<'a>(
ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
closure_data_layout: closure_layout,
}
};
@ -9560,11 +9725,10 @@ fn union_lambda_set_branch<'a>(
)
}
#[derive(Clone, Copy)]
enum ClosureInfo<'a> {
Captures {
closure_data_symbol: Symbol,
/// The layout of this closure variant
closure_data_layout: Layout<'a>,
/// The whole lambda set representation this closure is a variant of
lambda_set: LambdaSet<'a>,
},
@ -9586,34 +9750,21 @@ fn union_lambda_set_branch_help<'a>(
ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
closure_data_layout,
} => match closure_data_layout {
Layout::Struct {
field_layouts: &[], ..
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
(argument_layouts_slice, argument_symbols_slice)
}
_ => {
// extend layouts with the layout of the closure environment
let mut argument_layouts =
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
argument_layouts.extend(argument_layouts_slice);
argument_layouts.push(Layout::LambdaSet(lambda_set));
} => {
let argument_layouts =
lambda_set.extend_argument_list(env.arena, argument_layouts_slice);
let argument_symbols = if argument_layouts.len() > argument_layouts_slice.len() {
// extend symbols with the symbol of the closure environment
let mut argument_symbols =
Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena);
argument_symbols.extend(argument_symbols_slice);
argument_symbols.push(closure_data_symbol);
(
argument_layouts.into_bump_slice(),
argument_symbols.into_bump_slice(),
)
}
},
argument_symbols.into_bump_slice()
} else {
argument_symbols_slice
};
(argument_layouts, argument_symbols)
}
ClosureInfo::DoesNotCapture => {
// sometimes unification causes a function that does not itself capture anything
// to still get a lambda set that does store information. We must not pass a closure
@ -9637,13 +9788,14 @@ fn union_lambda_set_branch_help<'a>(
build_call(env, call, assigned, *return_layout, hole)
}
/// Switches over a enum lambda set, which may dispatch to different functions, none of which
/// capture.
#[allow(clippy::too_many_arguments)]
fn enum_lambda_set_to_switch<'a>(
env: &mut Env<'a, '_>,
lambda_set: impl ExactSizeIterator<Item = LambdaName<'a>>,
closure_tag_id_symbol: Symbol,
closure_tag_id_layout: Layout<'a>,
closure_data_symbol: Symbol,
argument_symbols: &'a [Symbol],
argument_layouts: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
@ -9656,15 +9808,11 @@ fn enum_lambda_set_to_switch<'a>(
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
let closure_layout = closure_tag_id_layout;
for (i, lambda_name) in lambda_set.into_iter().enumerate() {
let stmt = enum_lambda_set_branch(
env,
join_point_id,
lambda_name,
closure_data_symbol,
closure_layout,
argument_symbols,
argument_layouts,
return_layout,
@ -9700,15 +9848,14 @@ fn enum_lambda_set_to_switch<'a>(
}
}
/// A branch for an enum lambda set branch dispatch, which never capture!
#[allow(clippy::too_many_arguments)]
fn enum_lambda_set_branch<'a>(
env: &mut Env<'a, '_>,
join_point_id: JoinPointId,
lambda_name: LambdaName<'a>,
closure_data_symbol: Symbol,
closure_data_layout: Layout<'a>,
argument_symbols_slice: &'a [Symbol],
argument_layouts_slice: &'a [Layout<'a>],
argument_symbols: &'a [Symbol],
argument_layouts: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
) -> Stmt<'a> {
let result_symbol = env.unique_symbol();
@ -9717,34 +9864,6 @@ fn enum_lambda_set_branch<'a>(
let assigned = result_symbol;
let (argument_layouts, argument_symbols) = match closure_data_layout {
Layout::Struct {
field_layouts: &[], ..
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
(argument_layouts_slice, argument_symbols_slice)
}
_ => {
// extend layouts with the layout of the closure environment
let mut argument_layouts =
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
argument_layouts.extend(argument_layouts_slice);
argument_layouts.push(closure_data_layout);
// extend symbols with the symbol of the closure environment
let mut argument_symbols =
Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena);
argument_symbols.extend(argument_symbols_slice);
argument_symbols.push(closure_data_symbol);
(
argument_layouts.into_bump_slice(),
argument_symbols.into_bump_slice(),
)
}
};
let call = self::Call {
call_type: CallType::ByName {
name: lambda_name,

View file

@ -263,7 +263,7 @@ pub enum Layout<'a> {
/// so keep a hash of the record order for disambiguation. This still of course may result
/// in collisions, but it's unlikely.
///
/// See also https://github.com/rtfeldman/roc/issues/2535.
/// See also https://github.com/roc-lang/roc/issues/2535.
field_order_hash: FieldOrderHash,
field_layouts: &'a [Layout<'a>],
},
@ -731,7 +731,7 @@ impl std::fmt::Debug for LambdaSet<'_> {
/// By recording the captures layouts this lambda expects in its identifier, we can distinguish
/// between such differences when constructing closure capture data.
///
/// See also https://github.com/rtfeldman/roc/issues/3336.
/// See also https://github.com/roc-lang/roc/issues/3336.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct CapturesNiche<'a>(&'a [Layout<'a>]);
@ -783,28 +783,56 @@ impl<'a> LambdaName<'a> {
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LambdaSet<'a> {
/// collection of function names and their closure arguments
pub set: &'a [(Symbol, &'a [Layout<'a>])],
set: &'a [(Symbol, &'a [Layout<'a>])],
/// how the closure will be represented at runtime
representation: &'a Layout<'a>,
}
#[derive(Debug)]
pub enum EnumDispatch {
Bool,
U8,
}
/// representation of the closure *for a particular function*
#[derive(Debug)]
pub enum ClosureRepresentation<'a> {
/// the closure is represented as a union. Includes the tag ID!
/// The closure is represented as a union. Includes the tag ID!
/// Each variant is a different function, and its payloads are the captures.
Union {
alphabetic_order_fields: &'a [Layout<'a>],
closure_name: Symbol,
tag_id: TagIdIntType,
union_layout: UnionLayout<'a>,
},
/// The closure is represented as a struct. The layouts are sorted
/// alphabetically by the identifier that is captured.
/// The closure is one function, whose captures are represented as a struct.
/// The layouts are sorted alphabetically by the identifier that is captured.
///
/// We MUST sort these according to their stack size before code gen!
AlphabeticOrderStruct(&'a [Layout<'a>]),
/// the representation is anything but a union
Other(Layout<'a>),
/// The closure is one function that captures a single identifier, whose value is unwrapped.
UnwrappedCapture(Layout<'a>),
/// The closure dispatches to multiple functions, but none of them capture anything, so this is
/// a boolean or integer flag.
EnumDispatch(EnumDispatch),
}
/// How the closure should be seen when determining a call-by-name.
#[derive(Debug)]
pub enum ClosureCallOptions<'a> {
/// This is an empty lambda set, dispatching is an error
Void,
/// One of a few capturing functions can be called to
Union(UnionLayout<'a>),
/// The closure is one function, whose captures are represented as a struct.
Struct {
field_layouts: &'a [Layout<'a>],
field_order_hash: FieldOrderHash,
},
/// The closure is one function that captures a single identifier, whose value is unwrapped.
UnwrappedCapture(Layout<'a>),
/// The closure dispatches to multiple possible functions, none of which capture.
EnumDispatch(EnumDispatch),
}
impl<'a> LambdaSet<'a> {
@ -818,13 +846,17 @@ impl<'a> LambdaSet<'a> {
}
pub fn is_represented(&self) -> Option<Layout<'a>> {
match self.representation {
Layout::Struct {
field_layouts: &[], ..
if self.has_unwrapped_capture_repr() {
Some(*self.representation)
} else if self.has_enum_dispatch_repr() {
None
} else {
match self.representation {
Layout::Struct {
field_layouts: &[], ..
} => None,
repr => Some(*repr),
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(..)) => None,
repr => Some(*repr),
}
}
@ -835,6 +867,16 @@ impl<'a> LambdaSet<'a> {
})
}
#[inline(always)]
pub fn len(&self) -> usize {
self.set.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.set.is_empty()
}
pub fn layout_for_member_with_lambda_name(
&self,
lambda_name: LambdaName,
@ -923,6 +965,11 @@ impl<'a> LambdaSet<'a> {
where
F: Fn(Symbol, &[Layout]) -> bool,
{
if self.has_unwrapped_capture_repr() {
// Only one function, that captures one identifier.
return ClosureRepresentation::UnwrappedCapture(*self.representation);
}
match self.representation {
Layout::Union(union) => {
// here we rely on the fact that a union in a closure would be stored in a one-element record.
@ -1004,7 +1051,58 @@ impl<'a> LambdaSet<'a> {
ClosureRepresentation::AlphabeticOrderStruct(fields)
}
_ => ClosureRepresentation::Other(*self.representation),
layout => {
debug_assert!(self.has_enum_dispatch_repr(),);
let enum_repr = match layout {
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
other => internal_error!("Invalid layout for enum dispatch: {:?}", other),
};
ClosureRepresentation::EnumDispatch(enum_repr)
}
}
}
fn has_unwrapped_capture_repr(&self) -> bool {
self.set.len() == 1 && self.set[0].1.len() == 1
}
fn has_enum_dispatch_repr(&self) -> bool {
self.set.len() > 1 && self.set.iter().all(|(_, captures)| captures.is_empty())
}
pub fn call_by_name_options(&self) -> ClosureCallOptions<'a> {
if self.has_unwrapped_capture_repr() {
return ClosureCallOptions::UnwrappedCapture(*self.representation);
}
match self.representation {
Layout::Union(union_layout) => {
if self.representation == &Layout::VOID {
debug_assert!(self.set.is_empty());
return ClosureCallOptions::Void;
}
ClosureCallOptions::Union(*union_layout)
}
Layout::Struct {
field_layouts,
field_order_hash,
} => {
debug_assert_eq!(self.set.len(), 1);
ClosureCallOptions::Struct {
field_layouts,
field_order_hash: *field_order_hash,
}
}
layout => {
debug_assert!(self.has_enum_dispatch_repr());
let enum_repr = match layout {
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
other => internal_error!("Invalid layout for enum dispatch: {:?}", other),
};
ClosureCallOptions::EnumDispatch(enum_repr)
}
}
}
@ -1013,30 +1111,27 @@ impl<'a> LambdaSet<'a> {
arena: &'a Bump,
argument_layouts: &'a [Layout<'a>],
) -> &'a [Layout<'a>] {
if let [] = self.set {
// TERRIBLE HACK for builting functions
argument_layouts
} else {
match self.representation {
Layout::Struct {
field_layouts: &[], ..
} => {
// this function does not have anything in its closure, and the lambda set is a
// singleton, so we pass no extra argument
argument_layouts
}
Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8)) => {
// we don't pass this along either
argument_layouts
}
_ => {
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
arguments.extend(argument_layouts);
arguments.push(Layout::LambdaSet(*self));
match self.call_by_name_options() {
ClosureCallOptions::Void => argument_layouts,
ClosureCallOptions::Struct {
field_layouts: &[], ..
} => {
// this function does not have anything in its closure, and the lambda set is a
// singleton, so we pass no extra argument
argument_layouts
}
ClosureCallOptions::Struct { .. }
| ClosureCallOptions::Union(_)
| ClosureCallOptions::UnwrappedCapture(_) => {
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
arguments.extend(argument_layouts);
arguments.push(Layout::LambdaSet(*self));
arguments.into_bump_slice()
}
arguments.into_bump_slice()
}
ClosureCallOptions::EnumDispatch(_) => {
// No captures, don't pass this along
argument_layouts
}
}
}
@ -1053,7 +1148,7 @@ impl<'a> LambdaSet<'a> {
lambdas.sort_by_key(|(sym, _)| *sym);
let mut set: Vec<(Symbol, &[Layout])> = Vec::with_capacity_in(lambdas.len(), arena);
let mut set_with_variables: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)> =
let mut set_with_variables: std::vec::Vec<(&Symbol, &[Variable])> =
std::vec::Vec::with_capacity(lambdas.len());
let mut last_function_symbol = None;
@ -1090,7 +1185,7 @@ impl<'a> LambdaSet<'a> {
has_duplicate_lambda_names = has_duplicate_lambda_names || is_multimorphic;
set.push((*function_symbol, arguments));
set_with_variables.push((*function_symbol, variables.to_vec()));
set_with_variables.push((function_symbol, variables.as_slice()));
last_function_symbol = Some(function_symbol);
}
@ -1139,7 +1234,7 @@ impl<'a> LambdaSet<'a> {
}
ResolvedLambdaSet::Unbound => {
// The lambda set is unbound which means it must be unused. Just give it the empty lambda set.
// See also https://github.com/rtfeldman/roc/issues/3163.
// See also https://github.com/roc-lang/roc/issues/3163.
Ok(LambdaSet {
set: &[],
representation: arena.alloc(Layout::UNIT),
@ -1151,70 +1246,23 @@ impl<'a> LambdaSet<'a> {
fn make_representation(
arena: &'a Bump,
subs: &Subs,
tags: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)>,
tags: std::vec::Vec<(&Symbol, &[Variable])>,
opt_rec_var: Option<Variable>,
target_info: TargetInfo,
) -> Layout<'a> {
if let Some(rec_var) = opt_rec_var {
let tags: std::vec::Vec<_> = tags
.iter()
.map(|(sym, vars)| (sym, vars.as_slice()))
.collect();
let tags = UnsortedUnionLabels { tags };
let mut env = Env {
seen: Vec::new_in(arena),
target_info,
arena,
subs,
};
let union_labels = UnsortedUnionLabels { tags };
let mut env = Env {
seen: Vec::new_in(arena),
target_info,
arena,
subs,
};
return layout_from_recursive_union(&mut env, rec_var, &tags)
.expect("unable to create lambda set representation");
}
match opt_rec_var {
Some(rec_var) => layout_from_recursive_union(&mut env, rec_var, &union_labels)
.expect("unable to create lambda set representation"),
// otherwise, this is a closure with a payload
let variant = union_sorted_tags_help(arena, tags, opt_rec_var, subs, target_info);
use UnionVariant::*;
match variant {
Never => Layout::VOID,
BoolUnion { .. } => Layout::bool(),
ByteUnion { .. } => Layout::u8(),
Unit | UnitWithArguments => {
// no useful information to store
Layout::UNIT
}
Newtype {
arguments: layouts, ..
} => Layout::struct_no_name_order(layouts.into_bump_slice()),
Wrapped(variant) => {
use WrappedVariant::*;
match variant {
NonRecursive {
sorted_tag_layouts: tags,
} => {
debug_assert!(tags.len() > 1);
// if the closed-over value is actually a layout, it should be wrapped in a 1-element record
debug_assert!(matches!(tags[0].0, TagOrClosure::Closure(_)));
let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena);
for (_, tag_args) in tags.iter() {
tag_arguments.push(&tag_args[0..]);
}
Layout::Union(UnionLayout::NonRecursive(tag_arguments.into_bump_slice()))
}
Recursive { .. }
| NullableUnwrapped { .. }
| NullableWrapped { .. }
| NonNullableUnwrapped { .. } => {
internal_error!("Recursive layouts should be produced in an earlier branch")
}
}
}
None => layout_from_union(&mut env, &union_labels),
}
}
@ -1239,7 +1287,7 @@ enum ResolvedLambdaSet {
OptVariable,
),
/// TODO: figure out if this can happen in a correct program, or is the result of a bug in our
/// compiler. See https://github.com/rtfeldman/roc/issues/3163.
/// compiler. See https://github.com/roc-lang/roc/issues/3163.
Unbound,
}
@ -1777,6 +1825,13 @@ impl<'a> Layout<'a> {
}
}
}
pub fn runtime_representation(&self) -> Self {
match self {
Layout::LambdaSet(lambda_set) => lambda_set.runtime_representation(),
other => *other,
}
}
}
/// Avoid recomputing Layout from Variable multiple times.

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod borrow;

View file

@ -13,12 +13,12 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
bumpalo = { version = "3.8.0", features = ["collections"] }
encode_unicode = "0.3.6"
encode_unicode = "1.0.0"
[dev-dependencies]
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]}
pretty_assertions = "1.0.0"
indoc = "1.0.3"
indoc = "1.0.7"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
roc_test_utils = { path = "../../test_utils" }

View file

@ -1,11 +1,54 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "arbitrary"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
[[package]]
name = "backtrace"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitmaps"
version = "2.1.0"
@ -16,16 +59,40 @@ dependencies = [
]
[[package]]
name = "bumpalo"
version = "3.4.0"
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "bumpalo"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]]
name = "cc"
version = "1.0.61"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "encode_unicode"
@ -34,13 +101,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "im"
version = "14.3.0"
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "696059c87b83c5a258817ecd67c3af915e3ed141891fc35a1e79908801cf0ce7"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
"bumpalo",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "im"
version = "15.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
dependencies = [
"bitmaps",
"rand_core 0.5.1",
"rand_core",
"rand_xoshiro",
"sized-chunks",
"typenum",
@ -49,30 +155,30 @@ dependencies = [
[[package]]
name = "im-rc"
version = "14.3.0"
version = "15.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "303f7e6256d546e01979071417432425f15c1891fb309a5f2d724ee908fabd6e"
checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
dependencies = [
"bitmaps",
"rand_core 0.5.1",
"rand_core",
"rand_xoshiro",
"sized-chunks",
"typenum",
"version_check",
]
[[package]]
name = "inlinable_string"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.131"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40"
[[package]]
name = "libfuzzer-sys"
version = "0.3.4"
@ -84,54 +190,114 @@ dependencies = [
]
[[package]]
name = "rand_core"
version = "0.4.2"
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
[[package]]
name = "object"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "proc-macro2"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand_core"
version = "0.5.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
[[package]]
name = "rand_xoshiro"
version = "0.4.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
dependencies = [
"rand_core 0.5.1",
"rand_core",
]
[[package]]
name = "roc_collections"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bitvec",
"bumpalo",
"hashbrown",
"im",
"im-rc",
"wyhash",
]
[[package]]
name = "roc_error_macros"
version = "0.0.1"
[[package]]
name = "roc_ident"
version = "0.0.1"
[[package]]
name = "roc_module"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bumpalo",
"inlinable_string",
"lazy_static",
"roc_collections",
"roc_error_macros",
"roc_ident",
"roc_region",
"snafu",
"static_assertions",
]
[[package]]
name = "roc_parse"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bumpalo",
"encode_unicode",
"inlinable_string",
"roc_collections",
"roc_module",
"roc_region",
@ -148,24 +314,85 @@ dependencies = [
[[package]]
name = "roc_region"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"static_assertions",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "sized-chunks"
version = "0.5.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59044ea371ad781ff976f7b06480b9f0180e834eda94114f2afb4afc12b7718"
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
dependencies = [
"bitmaps",
"typenum",
]
[[package]]
name = "snafu"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
dependencies = [
"backtrace",
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicode-ident"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "version_check"
version = "0.9.2"
@ -173,10 +400,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "wyhash"
version = "0.3.0"
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "782a50f48ac4336916227cd199c61c7b42f38d0ad705421b49eb12c74c53ae00"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wyhash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295"
dependencies = [
"rand_core 0.4.2",
"rand_core",
]
[[package]]
name = "wyz"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
dependencies = [
"tap",
]

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
#[macro_use]

View file

@ -173,8 +173,8 @@ mod test_parse {
pass/lowest_float.expr,
pass/lowest_int.expr,
pass/malformed_ident_due_to_underscore.expr,
pass/malformed_pattern_field_access.expr, // See https://github.com/rtfeldman/roc/issues/399
pass/malformed_pattern_module_name.expr, // See https://github.com/rtfeldman/roc/issues/399
pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399
pass/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399
pass/minimal_app_header.header,
pass/minus_twelve_minus_five.expr,
pass/mixed_docs.expr,
@ -191,7 +191,7 @@ mod test_parse {
pass/nested_def_annotation.module,
pass/nested_if.expr,
pass/nested_module.header,
pass/newline_after_equals.expr, // Regression test for https://github.com/rtfeldman/roc/issues/51
pass/newline_after_equals.expr, // Regression test for https://github.com/roc-lang/roc/issues/51
pass/newline_after_mul.expr,
pass/newline_after_sub.expr,
pass/newline_and_spaces_before_less_than.expr,
@ -229,7 +229,7 @@ mod test_parse {
pass/parenthetical_var.expr,
pass/parse_alias.expr,
pass/parse_as_ann.expr,
pass/pattern_with_space_in_parens.expr, // https://github.com/rtfeldman/roc/issues/929
pass/pattern_with_space_in_parens.expr, // https://github.com/roc-lang/roc/issues/929
pass/plus_if.expr,
pass/plus_when.expr,
pass/pos_inf_float.expr,
@ -263,7 +263,7 @@ mod test_parse {
pass/two_branch_when.expr,
pass/two_spaced_def.expr,
pass/type_decl_with_underscore.expr,
pass/unary_negation_access.expr, // Regression test for https://github.com/rtfeldman/roc/issues/509
pass/unary_negation_access.expr, // Regression test for https://github.com/roc-lang/roc/issues/509
pass/unary_negation_arg.expr,
pass/unary_negation_with_parens.expr,
pass/unary_negation.expr,

View file

@ -38,6 +38,7 @@ pub enum Problem {
/// Bool is whether the closure is anonymous
/// Second symbol is the name of the argument that is unused
UnusedArgument(Symbol, bool, Symbol, Region),
UnusedBranchDef(Symbol, Region),
PrecedenceProblem(PrecedenceProblem),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(BadPattern, Region),

View file

@ -1,4 +1,4 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod can;

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod all;

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
use strum_macros::{EnumCount, EnumIter};

View file

@ -32,9 +32,9 @@ roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" }
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
pretty_assertions = "1.0.0"
indoc = "1.0.3"
indoc = "1.0.7"
tempfile = "3.2.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
regex = "1.5.5"
lazy_static = "1.4.0"
insta = "1.15.0"
insta = "1.18.2"

View file

@ -32,7 +32,7 @@ f = if True then id1 else id2
^ f : a -[[id1, id2]] -> a
```
The syntax `-[[id1]]->` can be read as “a function that dispatches to `id1`". Then the arrow `-[[id1, id2]]->` then is “a function that dispatches to `id1`, or `id2`". The tag union `[id1, id2]` can contain payloads to represent the captures of `id1` and `id2`; however, the implications of that are out of scope for this discussion, see [Folkerts great explanation](https://github.com/rtfeldman/roc/pull/2307#discussion_r777042512) for more information. During compile-time, Roc would attach a run-time examinable tag to the value in each branch of the `f` expression body, representing whether to dispatch to `id1` or `id2`. Whenever `f` is dispatched, that tag is examined to determine exactly which function should be dispatched to. This is “**defunctionalization**”.
The syntax `-[[id1]]->` can be read as “a function that dispatches to `id1`". Then the arrow `-[[id1, id2]]->` then is “a function that dispatches to `id1`, or `id2`". The tag union `[id1, id2]` can contain payloads to represent the captures of `id1` and `id2`; however, the implications of that are out of scope for this discussion, see [Folkerts great explanation](https://github.com/roc-lang/roc/pull/2307#discussion_r777042512) for more information. During compile-time, Roc would attach a run-time examinable tag to the value in each branch of the `f` expression body, representing whether to dispatch to `id1` or `id2`. Whenever `f` is dispatched, that tag is examined to determine exactly which function should be dispatched to. This is “**defunctionalization**”.
In the presence of [abilities](https://docs.google.com/document/d/1kUh53p1Du3fWP_jZp-sdqwb5C9DuS43YJwXHg1NzETY/edit), lambda sets get more complicated. Now, I can write something like
@ -100,7 +100,7 @@ The unification trace for the call `f (@Foo {})` proceeds as follows. I use `'tN
Foo -[[zeroHash] + Foo:hashThunk:1]-> ({} -[[lam1] + Foo:hashThunk:2]-> U64)
```
Now that the specialization lambdas type variables point to concrete types, we can resolve the concrete lambdas of `Foo:hashThunk:1` and `Foo:hashThunk:2`. Cool! Lets do that. We know that
Now that the specialization lambdas type variables point to concrete types, we can resolve the concrete lambdas of `Foo:hashThunk:1` and `Foo:hashThunk:2`. Cool! Lets do that. We know that
```
hashThunk = \@Foo {} -> \{} -> 1
@ -205,46 +205,46 @@ Okay, so first well enumerate some terminology, and the exact algorithm. Then
### Some definitions
- **The region invariant.** Previously we discussed the “region” of a lambda set in a specialization function definition. The way regions are assigned in the compiler follows a very specific ordering and holds a invariant well call the “region invariant”. First, lets define a procedure for creating function types and assigning regions:
```
Type = \region ->
Type = \region ->
(Type_atom, region)
| Type_function region
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)
```
This procedure would create functions that look like the trees(abbreviating `L=Lambda`, `a=atom` below)
```
-[L 1]->
a a
===
-[L 1]->
-[L 2]-> -[L 3]->
a a a a
===
-[L 1]->
-[L 2]-> -[L 5]->
-[L 3]-> -[L 4]-> -[L 6]-> -[L 7]->
a a a a a a a a
```
The invariant is this: for a region `r`, the only functions enclosing `r` have a region number that is less than `r`. Moreover, every region `r' < r`, either the function at `r'` encloses `r`, or is disjoint from `r`.
- **Ambient functions.** For a given lambda set at region `r`, any function that encloses `r` is called an **ambient function** of `r`. The function directly at region `r` is called the **directly ambient function**.
For example, the functions identified by `L 4`, `L 2`, and `L 1` in the last example tree above are all ambient functions of the function identified by `L 4`.
The region invariant means that the only functions that are ambient of a region `r` are those identified by regions `< r`.
The region invariant means that the only functions that are ambient of a region `r` are those identified by regions `< r`.
- `uls_of_var`. A look aside table of the unspecialized lambda sets (uls) depending on a variable. For example, in `a -[[] + a:f:1]-> (b -[[] + a:f:2]-> {})`, there would be a mapping of `a => { [[] + a:f:1]; [[] + a:f:2] }`. When `a` gets instantiated with a concrete type, we know that these lambda sets are ready to be resolved.
### Explicit Description
@ -440,13 +440,13 @@ F has f : a, b -> ({} -> ({} -> {})) | a has F, b has G
# ^ a, b -[[] + a:f:1]-> ({} -[[] + a:f:2]-> ({} -[[] + a:f:3]-> {})) | a has F, b has G
G has g : b -> ({} -> {}) | b has G
# ^ b -[[] + b:g:1]-> ({} -[[] + b:g:2]-> {}) | b has G
Fo := {}
f = \@Fo {}, b -> \{} -> g b
#^ Fo, b -[[Fo#f]]-> ({} -[[lamF b]]-> ({} -[[] + b:g:2]]-> {})) | b has G
# instantiation with a=Fo of
# a, b -[[] + a:f:1]-> ({} -[[] + a:f:2]-> ({} -[[] + a:f:3]-> {})) | a has F, b has G
Go := {}
g = \@Go {} -> \{} -> {}
#^ {} -[[Go#g]]-> ({} -[[lamG]]-> {})
@ -670,7 +670,7 @@ You may have observed that step 1 and step 2 of the algorithm are somewhat overk
This optimization is correct with a change to the region numbering scheme:
```python
Type = \region ->
Type = \region ->
(Type_atom, region)
| Type_function region

View file

@ -442,16 +442,6 @@ trait DerivableVisitor {
false
}
#[inline(always)]
fn visit_flex(var: Variable) -> Result<(), DerivableError> {
Err(DerivableError::NotDerivable(var))
}
#[inline(always)]
fn visit_rigid(var: Variable) -> Result<(), DerivableError> {
Err(DerivableError::NotDerivable(var))
}
#[inline(always)]
fn visit_flex_able(var: Variable, ability: Symbol) -> Result<(), DerivableError> {
if ability != Self::ABILITY {
@ -529,7 +519,7 @@ trait DerivableVisitor {
fn is_derivable(
obligation_cache: &mut ObligationCache,
abilities_store: &AbilitiesStore,
subs: &Subs,
subs: &mut Subs,
var: Variable,
) -> Result<(), DerivableError> {
let mut stack = vec![var];
@ -552,8 +542,11 @@ trait DerivableVisitor {
use DerivableError::*;
use FlatType::*;
match *content {
FlexVar(_) => Self::visit_flex(var)?,
RigidVar(_) => Self::visit_rigid(var)?,
FlexVar(opt_name) => {
// Promote the flex var to be bound to the ability.
subs.set_content(var, Content::FlexAbleVar(opt_name, Self::ABILITY));
}
RigidVar(_) => return Err(NotDerivable(var)),
FlexAbleVar(_, ability) => Self::visit_flex_able(var, ability)?,
RigidAbleVar(_, ability) => Self::visit_rigid_able(var, ability)?,
RecursionVar {
@ -584,7 +577,15 @@ trait DerivableVisitor {
let descend = Self::visit_record(var)?;
if descend.0 {
push_var_slice!(fields.variables());
stack.push(ext);
if !matches!(
subs.get_content_without_compacting(ext),
Content::FlexVar(_) | Content::RigidVar(_)
) {
// TODO: currently, just we suppose the presence of a flex var may
// include more or less things which we can derive. But, we should
// instead recurse here, and add a `t ~ u | u has Decode` constraint as needed.
stack.push(ext);
}
}
}
TagUnion(tags, ext) => {

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod ability;

View file

@ -3113,7 +3113,7 @@ fn adjust_rank_content(
// inside a lambda set but not on the left or right of an arrow, and records should not
// force de-generalization in such cases.
//
// See https://github.com/rtfeldman/roc/issues/3641 for a longer discussion and
// See https://github.com/roc-lang/roc/issues/3641 for a longer discussion and
// example.
group_rank
}

View file

@ -182,7 +182,13 @@ mod solve_expr {
// Disregard UnusedDef problems, because those are unavoidable when
// returning a function from the test expression.
can_problems.retain(|prob| !matches!(prob, roc_problem::can::Problem::UnusedDef(_, _)));
can_problems.retain(|prob| {
!matches!(
prob,
roc_problem::can::Problem::UnusedDef(_, _)
| roc_problem::can::Problem::UnusedBranchDef(..)
)
});
let (can_problems, type_problems) =
format_problems(&src, home, &interns, can_problems, type_problems);
@ -4916,7 +4922,7 @@ mod solve_expr {
#[test]
fn rigid_type_variable_problem() {
// see https://github.com/rtfeldman/roc/issues/1162
// see https://github.com/roc-lang/roc/issues/1162
infer_eq_without_problem(
indoc!(
r#"
@ -5024,7 +5030,7 @@ mod solve_expr {
#[test]
fn inference_var_tag_union_ext() {
// TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here.
// See https://github.com/rtfeldman/roc/issues/2053
// See https://github.com/roc-lang/roc/issues/2053
infer_eq_without_problem(
indoc!(
r#"
@ -5491,7 +5497,7 @@ mod solve_expr {
)
}
// https://github.com/rtfeldman/roc/issues/2379
// https://github.com/roc-lang/roc/issues/2379
#[test]
fn copy_vars_referencing_copied_vars() {
infer_eq_without_problem(
@ -5866,7 +5872,7 @@ mod solve_expr {
}
#[test]
// https://github.com/rtfeldman/roc/issues/2702
// https://github.com/roc-lang/roc/issues/2702
fn tag_inclusion_behind_opaque() {
infer_eq_without_problem(
indoc!(
@ -7691,4 +7697,68 @@ mod solve_expr {
"###
);
}
#[test]
fn transient_captures() {
infer_queries!(
indoc!(
r#"
x = "abc"
getX = \{} -> x
h = \{} -> (getX {})
#^{-1}
h {}
"#
),
@"h : {}* -[[h(3) Str]]-> Str"
);
}
#[test]
fn transient_captures_after_def_ordering() {
infer_queries!(
indoc!(
r#"
h = \{} -> (getX {})
#^{-1}
getX = \{} -> x
x = "abc"
h {}
"#
),
@"h : {}* -[[h(1) Str]]-> Str"
);
}
#[test]
fn mutually_recursive_captures() {
infer_queries!(
indoc!(
r#"
x = True
y = False
a = "foo"
b = "bar"
foo = \{} -> if x then a else bar {}
#^^^{-1}
bar = \{} -> if y then b else foo {}
#^^^{-1}
bar {}
"#
),
@r###"
foo : {} -[[foo(5) [True]* [False]* Str Str]]-> Str
bar : {} -[[bar(6) [True]* [False]* Str Str]]-> Str
"###
);
}
}

View file

@ -16,7 +16,7 @@ roc_builtins = { path = "../builtins" }
roc_load_internal = { path = "../load_internal" }
roc_can = { path = "../can" }
roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
roc_derive = { path = "../derive", features = ["debug-derived-symbols", "open-extension-vars"] }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
roc_reporting = { path = "../../reporting" }
@ -26,7 +26,7 @@ roc_solve = { path = "../solve" }
roc_debug_flags = { path = "../debug_flags" }
bumpalo = { version = "3.8.0", features = ["collections"] }
lazy_static = "1.4.0"
indoc = "1.0.3"
indoc = "1.0.7"
ven_pretty = { path = "../../vendor/pretty" }
pretty_assertions = "1.0.0"
insta = "1.15.0"
insta = "1.18.2"

View file

@ -5,14 +5,48 @@
#![allow(non_snake_case)]
use crate::{
util::{check_immediate, derive_test},
test_key_eq, test_key_neq,
util::{check_immediate, check_underivable, derive_test},
v,
};
use insta::assert_snapshot;
use roc_module::symbol::Symbol;
use roc_types::subs::Variable;
use roc_derive_key::DeriveBuiltin::Decoder;
use roc_derive_key::{DeriveBuiltin::Decoder, DeriveError};
test_key_eq! {
Decoder,
same_record:
v!({ a: v!(U8), }), v!({ a: v!(U8), })
same_record_fields_diff_types:
v!({ a: v!(U8), }), v!({ a: v!(STR), })
same_record_fields_any_order:
v!({ a: v!(U8), b: v!(U8), c: v!(U8), }),
v!({ c: v!(U8), a: v!(U8), b: v!(U8), })
explicit_empty_record_and_implicit_empty_record:
v!(EMPTY_RECORD), v!({})
list_list_diff_types:
v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8))
str_str:
v!(Symbol::STR_STR), v!(Symbol::STR_STR)
}
test_key_neq! {
Decoder,
different_record_fields:
v!({ a: v!(U8), }), v!({ b: v!(U8), })
record_empty_vs_nonempty:
v!(EMPTY_RECORD), v!({ a: v!(U8), })
}
#[test]
fn optional_record_field_derive_error() {
check_underivable(Decoder, v!({ ?a: v!(U8), }), DeriveError::Underivable);
}
#[test]
fn immediates() {
@ -49,3 +83,66 @@ fn list() {
)
})
}
#[test]
fn record_2_fields() {
derive_test(Decoder, v!({first: v!(STR), second: v!(STR),}), |golden| {
assert_snapshot!(golden, @r###"
# derived for { first : Str, second : Str }
# Decoder { first : val, second : val1 } fmt | fmt has DecoderFormatting, val has Decoding, val1 has Decoding
# List U8, fmt -[[custom(22)]]-> { rest : List U8, result : [Err [TooShort], Ok { first : val, second : val1 }] } | fmt has DecoderFormatting, val has Decoding, val1 has Decoding
# Specialization lambda sets:
# @<1>: [[custom(22)]]
#Derived.decoder_{first,second} =
Decode.custom
\#Derived.bytes3, #Derived.fmt3 ->
Decode.decodeWith
#Derived.bytes3
(Decode.record
{ second: Err NoField, first: Err NoField }
\#Derived.stateRecord2, #Derived.field ->
when #Derived.field is
"first" ->
Keep (Decode.custom
\#Derived.bytes, #Derived.fmt ->
when Decode.decodeWith
#Derived.bytes
Decode.decoder
#Derived.fmt is
#Derived.rec ->
{
result: when #Derived.rec.result is
Ok #Derived.val ->
Ok { stateRecord2 & first: Ok #Derived.val }
Err #Derived.err -> Err #Derived.err,
rest: #Derived.rec.rest
})
"second" ->
Keep (Decode.custom
\#Derived.bytes2, #Derived.fmt2 ->
when Decode.decodeWith
#Derived.bytes2
Decode.decoder
#Derived.fmt2 is
#Derived.rec2 ->
{
result: when #Derived.rec2.result is
Ok #Derived.val2 ->
Ok { stateRecord2 & second: Ok #Derived.val2 }
Err #Derived.err2 -> Err #Derived.err2,
rest: #Derived.rec2.rest
})
_ -> Skip
\#Derived.stateRecord ->
when #Derived.stateRecord.first is
Ok #Derived.first ->
when #Derived.stateRecord.second is
Ok #Derived.second ->
Ok { second: #Derived.second, first: #Derived.first }
_ -> Err TooShort
_ -> Err TooShort)
#Derived.fmt3
"###
)
})
}

View file

@ -7,7 +7,7 @@
use insta::assert_snapshot;
use crate::{
test_hash_eq, test_hash_neq,
test_key_eq, test_key_neq,
util::{check_immediate, derive_test},
v,
};
@ -17,7 +17,7 @@ use roc_types::subs::Variable;
// {{{ hash tests
test_hash_eq! {
test_key_eq! {
ToEncoder,
same_record:
@ -70,7 +70,7 @@ test_hash_eq! {
v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True]))
}
test_hash_neq! {
test_key_neq! {
ToEncoder,
different_record_fields:
@ -174,10 +174,7 @@ fn one_field_record() {
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
#Derived.bytes
(Encode.record
[
{ value: Encode.toEncoder #Derived.rcd.a, key: "a", },
])
(Encode.record [{ value: Encode.toEncoder #Derived.rcd.a, key: "a" }])
#Derived.fmt
"###
)
@ -202,8 +199,8 @@ fn two_field_record() {
#Derived.bytes
(Encode.record
[
{ value: Encode.toEncoder #Derived.rcd.a, key: "a", },
{ value: Encode.toEncoder #Derived.rcd.b, key: "b", },
{ value: Encode.toEncoder #Derived.rcd.a, key: "a" },
{ value: Encode.toEncoder #Derived.rcd.b, key: "b" },
])
#Derived.fmt
"###

View file

@ -19,7 +19,10 @@ pub fn pretty_print_def(c: &Ctx, d: &Def) -> String {
macro_rules! maybe_paren {
($paren_if_above:expr, $my_prec:expr, $doc:expr) => {
if $my_prec > $paren_if_above {
maybe_paren!($paren_if_above, $my_prec, || true, $doc)
};
($paren_if_above:expr, $my_prec:expr, $extra_cond:expr, $doc:expr) => {
if $my_prec > $paren_if_above && $extra_cond() {
$doc.parens().group()
} else {
$doc
@ -47,7 +50,7 @@ fn def<'a>(c: &Ctx, f: &'a Arena<'a>, d: &'a Def) -> DocBuilder<'a, Arena<'a>> {
#[derive(PartialEq, PartialOrd)]
enum EPrec {
Free,
CallArg,
AppArg,
}
fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> {
@ -137,11 +140,11 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
maybe_paren!(
Free,
p,
expr(c, CallArg, f, &fun.value)
expr(c, AppArg, f, &fun.value)
.append(
f.concat(args.iter().map(|le| f.line().append(expr(
c,
CallArg,
AppArg,
f,
&le.1.value
))))
@ -175,15 +178,18 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
Record { fields, .. } => f
.reflow("{")
.append(
f.concat(fields.iter().map(|(name, field)| {
let field = f
.text(name.as_str())
.append(f.reflow(": "))
.append(expr(c, Free, f, &field.loc_expr.value))
.nest(2)
.group();
f.line().append(field).append(",")
}))
f.intersperse(
fields.iter().map(|(name, field)| {
let field = f
.text(name.as_str())
.append(f.reflow(": "))
.append(expr(c, Free, f, &field.loc_expr.value))
.nest(2)
.group();
f.line().append(field)
}),
f.reflow(","),
)
.nest(2)
.group(),
)
@ -193,15 +199,61 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
EmptyRecord => f.text("{}"),
Access {
loc_expr, field, ..
} => expr(c, CallArg, f, &loc_expr.value)
} => expr(c, AppArg, f, &loc_expr.value)
.append(f.text(format!(".{}", field.as_str())))
.group(),
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
f.text(format!("@{}", opaque_name.as_str(c.interns)))
}
Accessor(_) => todo!(),
Update { .. } => todo!(),
Tag { .. } => todo!(),
Update {
symbol, updates, ..
} => f
.reflow("{")
.append(f.line())
.append(f.text(symbol.as_str(c.interns).to_string()))
.append(f.reflow(" &"))
.append(
f.intersperse(
updates.iter().map(|(name, field)| {
let field = f
.text(name.as_str())
.append(f.reflow(": "))
.append(expr(c, Free, f, &field.loc_expr.value))
.nest(2)
.group();
f.line().append(field)
}),
f.reflow(","),
)
.nest(2)
.group(),
)
.append(f.line())
.append(f.text("}"))
.group(),
Tag {
name, arguments, ..
} => maybe_paren!(
Free,
p,
|| !arguments.is_empty(),
f.text(name.0.as_str())
.append(if arguments.is_empty() {
f.nil()
} else {
f.space()
})
.append(
f.intersperse(
arguments
.iter()
.map(|(_, le)| expr(c, AppArg, f, &le.value)),
f.space(),
)
)
.group()
),
ZeroArgumentTag { .. } => todo!(),
OpaqueRef { .. } => todo!(),
Expect { .. } => todo!(),

View file

@ -18,7 +18,7 @@ use roc_collections::VecSet;
use roc_constrain::expr::constrain_decls;
use roc_debug_flags::dbg_do;
use roc_derive::DerivedModule;
use roc_derive_key::{DeriveBuiltin, DeriveKey, Derived};
use roc_derive_key::{DeriveBuiltin, DeriveError, DeriveKey, Derived};
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
use roc_module::symbol::{IdentIds, Interns, ModuleId, Symbol};
use roc_region::all::LineInfo;
@ -53,7 +53,7 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa
}
}
/// DSL for creating [`Content`][crate::subs::Content].
/// DSL for creating [`Content`][roc_types::subs::Content].
#[macro_export]
macro_rules! v {
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {{
@ -65,7 +65,7 @@ macro_rules! v {
$(let $opt_field = $make_opt_v(subs);)*
let fields = vec![
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
$( (stringify!($opt_field).into(), RecordField::Optional($opt_field)) ,)*
];
let fields = RecordFields::insert_into_subs(subs, fields);
roc_derive::synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
@ -178,7 +178,7 @@ where
}
#[macro_export]
macro_rules! test_hash_eq {
macro_rules! test_key_eq {
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
#[test]
fn $name() {
@ -188,7 +188,7 @@ macro_rules! test_hash_eq {
}
#[macro_export]
macro_rules! test_hash_neq {
macro_rules! test_key_neq {
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
#[test]
fn $name() {
@ -197,6 +197,18 @@ macro_rules! test_hash_neq {
)*};
}
pub(crate) fn check_underivable<Sy>(builtin: DeriveBuiltin, synth: Sy, err: DeriveError)
where
Sy: FnOnce(&mut Subs) -> Variable,
{
let mut subs = Subs::new();
let var = synth(&mut subs);
let key = Derived::builtin(builtin, &subs, var);
assert_eq!(key, Err(err));
}
pub(crate) fn check_immediate<S>(builtin: DeriveBuiltin, synth: S, immediate: Symbol)
where
S: FnOnce(&mut Subs) -> Variable,
@ -324,7 +336,7 @@ fn check_derived_typechecks_and_golden(
// run the solver, print and fail if we have errors
dbg_do!(
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1")
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS, "1")
);
let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve(
test_module,
@ -338,6 +350,10 @@ fn check_derived_typechecks_and_golden(
&exposed_for_module.exposed_by_module,
Default::default(),
);
dbg_do!(
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS, "0")
);
let subs = solved_subs.inner_mut();
if !problems.is_empty() {

View file

@ -43,9 +43,9 @@ inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.3"
libloading = "0.7.1"
tempfile = "3.2.0"
indoc = "1.0.3"
indoc = "1.0.7"
criterion = { git = "https://github.com/Anton-4/criterion.rs" }
wasm3 = "0.3.1"
wasm3 = { git = "https://github.com/roc-lang/wasm3-rs", rev = "f0f807d1fc0a50d1d68e5799e54ee62c05af00f5" }
lazy_static = "1.4.0"
[features]

View file

@ -468,9 +468,9 @@ mod encode_immediate {
17, u32
17, u64
17, u128
// 17.23, f32 TODO https://github.com/rtfeldman/roc/issues/3522
17.25, f32
17.23, f64
// 17.23, dec TODO https://github.com/rtfeldman/roc/issues/3522
17.23, dec
}
}
@ -691,7 +691,6 @@ fn encode_derived_list_of_records() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
fn encode_derived_list_of_lists_of_strings() {
assert_evals_to!(
indoc!(
@ -715,10 +714,7 @@ fn encode_derived_list_of_lists_of_strings() {
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(feature = "gen-llvm-wasm") // hits a stack limit in wasm3
))]
#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))]
fn encode_derived_record_with_many_types() {
assert_evals_to!(
indoc!(
@ -877,7 +873,7 @@ mod decode_immediate {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn dec() {
use roc_std::RocDec;
@ -899,7 +895,7 @@ mod decode_immediate {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_list_of_strings() {
assert_evals_to!(
indoc!(
@ -918,10 +914,7 @@ fn decode_list_of_strings() {
}
#[test]
#[cfg(all(
any(feature = "gen-llvm"), // currently fails on gen-wasm
not(feature = "gen-llvm-wasm") // hits a stack limit in wasm3
))]
#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))]
fn encode_then_decode_list_of_strings() {
assert_evals_to!(
indoc!(
@ -958,3 +951,142 @@ fn encode_then_decode_list_of_lists_of_strings() {
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_record_two_fields() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8 is
Ok {first: "ab", second: "cd"} -> "abcd"
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_record_two_fields_string_and_int() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"first\":\"ab\",\"second\":10}" |> Decode.fromBytes Json.fromUtf8 is
Ok {first: "ab", second: 10u8} -> "ab10"
_ -> "something went wrong"
"#
),
RocStr::from("ab10"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_record_two_fields_string_and_string_infer() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8 is
Ok {first, second} -> Str.concat first second
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_record_two_fields_string_and_string_infer_local_var() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Decode, Json] provides [main] to "./platform"
main =
decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8
when decoded is
Ok rcd -> Str.concat rcd.first rcd.second
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_record_two_fields_string_and_string_infer_local_var_destructured() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Decode, Json] provides [main] to "./platform"
main =
decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8
when decoded is
Ok {first, second} -> Str.concat first second
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore = "json parsing impl must be fixed first"]
fn decode_empty_record() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{}" |> Decode.fromBytes Json.fromUtf8 is
Ok {} -> "empty"
_ -> "something went wrong"
"#
),
RocStr::from("empty"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(feature = "gen-llvm-wasm") // hits a wasm3 stack overflow
))]
fn decode_record_of_record() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"outer\":{\"inner\":\"a\"},\"other\":{\"one\":\"b\",\"two\":10}}" |> Decode.fromBytes Json.fromUtf8 is
Ok {outer: {inner: "a"}, other: {one: "b", two: 10u8}} -> "ab10"
_ -> "something went wrong"
"#
),
RocStr::from("ab10"),
RocStr
)
}

View file

@ -2831,7 +2831,7 @@ fn lists_with_incompatible_type_param_in_if() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn map_with_index_multi_record() {
// see https://github.com/rtfeldman/roc/issues/1700
// see https://github.com/roc-lang/roc/issues/1700
assert_evals_to!(
indoc!(
r#"
@ -2846,7 +2846,7 @@ fn map_with_index_multi_record() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn empty_list_of_function_type() {
// see https://github.com/rtfeldman/roc/issues/1732
// see https://github.com/roc-lang/roc/issues/1732
assert_evals_to!(
indoc!(
r#"
@ -3276,7 +3276,7 @@ fn monomorphized_lists() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn with_capacity() {
// see https://github.com/rtfeldman/roc/issues/1732
// see https://github.com/roc-lang/roc/issues/1732
assert_evals_to!(
indoc!(
r#"
@ -3369,3 +3369,23 @@ fn issue_3530_uninitialized_capacity_in_list_literal() {
|(_, _, cap)| cap
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_let_generalization() {
assert_evals_to!(
indoc!(
r#"
empty : List a
empty = []
xs : List Str
xs = List.append empty "foo"
List.len xs
"#
),
1,
usize
);
}

View file

@ -1114,18 +1114,35 @@ fn gen_mul_dec() {
i128
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn gen_mul_i64() {
assert_evals_to!(
indoc!(
r#"
2 * 4 * 6
"#
),
48,
i64
);
fn gen_signed_mul_quadword_and_lower() {
assert_evals_to!("2i64 * 4 * 6", 48, i64);
assert_evals_to!("2i32 * 4 * 6", 48, i32);
assert_evals_to!("2i16 * 4 * 6", 48, i16);
assert_evals_to!("2i8 * 4 * 6", 48, i8);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn gen_unsigned_mul_quadword_and_lower() {
assert_evals_to!("2u64 * 4 * 6", 48, u64);
assert_evals_to!("2u32 * 4 * 6", 48, u32);
assert_evals_to!("2u16 * 4 * 6", 48, u16);
assert_evals_to!("2u8 * 4 * 6", 48, u8);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn gen_mul_f64() {
assert_evals_to!("2f64 * 4 * 6", 48.0, f64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn gen_mul_f32() {
assert_evals_to!("2f32 * 4 * 6", 48.0, f32);
}
#[test]
@ -2091,9 +2108,9 @@ fn float_mul_checked() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn shift_left_by() {
assert_evals_to!("Num.shiftLeftBy 0 0b0000_0001", 0b0000_0001, i64);
assert_evals_to!("Num.shiftLeftBy 1 0b0000_0001", 0b0000_0010, i64);
assert_evals_to!("Num.shiftLeftBy 2 0b0000_0011", 0b0000_1100, i64);
assert_evals_to!("Num.shiftLeftBy 0b0000_0001 0", 0b0000_0001, i64);
assert_evals_to!("Num.shiftLeftBy 0b0000_0001 1", 0b0000_0010, i64);
assert_evals_to!("Num.shiftLeftBy 0b0000_0011 2", 0b0000_1100, i64);
}
#[test]
@ -2105,43 +2122,43 @@ fn shift_right_by() {
// FIXME (Brian) Something funny happening with 8-bit binary literals in tests
assert_evals_to!(
"Num.shiftRightBy 2 (Num.toI8 0b1100_0000u8)",
"Num.shiftRightBy (Num.toI8 0b1100_0000u8) 2",
0b1111_0000u8 as i8,
i8
);
assert_evals_to!("Num.shiftRightBy 2 0b0100_0000i8", 0b0001_0000i8, i8);
assert_evals_to!("Num.shiftRightBy 1 0b1110_0000u8", 0b1111_0000u8, u8);
assert_evals_to!("Num.shiftRightBy 2 0b1100_0000u8", 0b1111_0000u8, u8);
assert_evals_to!("Num.shiftRightBy 12 0b0100_0000u8", 0b0000_0000u8, u8);
assert_evals_to!("Num.shiftRightBy 0b0100_0000i8 2", 0b0001_0000i8, i8);
assert_evals_to!("Num.shiftRightBy 0b1110_0000u8 1", 0b1111_0000u8, u8);
assert_evals_to!("Num.shiftRightBy 0b1100_0000u8 2", 0b1111_0000u8, u8);
assert_evals_to!("Num.shiftRightBy 0b0100_0000u8 12", 0b0000_0000u8, u8);
// LLVM in release mode returns 0 instead of -1 for some reason
if !is_llvm_release_mode {
assert_evals_to!("Num.shiftRightBy 12 0b1000_0000u8", 0b1111_1111u8, u8);
assert_evals_to!("Num.shiftRightBy 0b1000_0000u8 12", 0b1111_1111u8, u8);
}
assert_evals_to!("Num.shiftRightBy 0 12", 12, i64);
assert_evals_to!("Num.shiftRightBy 1 12", 6, i64);
assert_evals_to!("Num.shiftRightBy 1 -12", -6, i64);
assert_evals_to!("Num.shiftRightBy 8 12", 0, i64);
assert_evals_to!("Num.shiftRightBy 8 -12", -1, i64);
assert_evals_to!("Num.shiftRightBy -1 12", 0, i64);
assert_evals_to!("Num.shiftRightBy 12 0", 12, i64);
assert_evals_to!("Num.shiftRightBy 12 1", 6, i64);
assert_evals_to!("Num.shiftRightBy -12 1", -6, i64);
assert_evals_to!("Num.shiftRightBy 12 8", 0, i64);
assert_evals_to!("Num.shiftRightBy -12 8", -1, i64);
assert_evals_to!("Num.shiftRightBy 12 -1", 0, i64);
assert_evals_to!("Num.shiftRightBy 0 0", 0, i64);
assert_evals_to!("Num.shiftRightBy 1 0", 0, i64);
assert_evals_to!("Num.shiftRightBy 0 1", 0, i64);
assert_evals_to!("Num.shiftRightBy 0 12i32", 12, i32);
assert_evals_to!("Num.shiftRightBy 1 12i32", 6, i32);
assert_evals_to!("Num.shiftRightBy 1 -12i32", -6, i32);
assert_evals_to!("Num.shiftRightBy 8 12i32", 0, i32);
assert_evals_to!("Num.shiftRightBy 8 -12i32", -1, i32);
assert_evals_to!("Num.shiftRightBy 12i32 0", 12, i32);
assert_evals_to!("Num.shiftRightBy 12i32 1", 6, i32);
assert_evals_to!("Num.shiftRightBy -12i32 1", -6, i32);
assert_evals_to!("Num.shiftRightBy 12i32 8", 0, i32);
assert_evals_to!("Num.shiftRightBy -12i32 8", -1, i32);
assert_evals_to!("Num.shiftRightBy 0 12i8", 12, i8);
assert_evals_to!("Num.shiftRightBy 1 12i8", 6, i8);
assert_evals_to!("Num.shiftRightBy 1 -12i8", -6, i8);
assert_evals_to!("Num.shiftRightBy 8 12i8", 0, i8);
assert_evals_to!("Num.shiftRightBy 12i8 0", 12, i8);
assert_evals_to!("Num.shiftRightBy 12i8 1", 6, i8);
assert_evals_to!("Num.shiftRightBy -12i8 1", -6, i8);
assert_evals_to!("Num.shiftRightBy 12i8 8", 0, i8);
if !is_llvm_release_mode {
assert_evals_to!("Num.shiftRightBy -1 0", 0, i64);
assert_evals_to!("Num.shiftRightBy -1 -12", -1, i64);
assert_evals_to!("Num.shiftRightBy 8 -12i8", -1, i8);
assert_evals_to!("Num.shiftRightBy 0 -1", 0, i64);
assert_evals_to!("Num.shiftRightBy -12 -1", -1, i64);
assert_evals_to!("Num.shiftRightBy -12i8 8", -1, i8);
}
}
@ -2150,14 +2167,14 @@ fn shift_right_by() {
fn shift_right_zf_by() {
// Logical Right Shift
assert_evals_to!(
"Num.shiftRightZfBy 2 (Num.toI8 0b1100_0000u8)",
"Num.shiftRightZfBy (Num.toI8 0b1100_0000u8) 2",
0b0011_0000i8,
i8
);
assert_evals_to!("Num.shiftRightZfBy 2 0b1100_0000u8", 0b0011_0000u8, u8);
assert_evals_to!("Num.shiftRightZfBy 1 0b0000_0010u8", 0b0000_0001u8, u8);
assert_evals_to!("Num.shiftRightZfBy 2 0b0000_1100u8", 0b0000_0011u8, u8);
assert_evals_to!("Num.shiftRightZfBy 12 0b1000_0000u8", 0b0000_0000u8, u8);
assert_evals_to!("Num.shiftRightZfBy 0b1100_0000u8 2", 0b0011_0000u8, u8);
assert_evals_to!("Num.shiftRightZfBy 0b0000_0010u8 1", 0b0000_0001u8, u8);
assert_evals_to!("Num.shiftRightZfBy 0b0000_1100u8 2", 0b0000_0011u8, u8);
assert_evals_to!("Num.shiftRightZfBy 0b1000_0000u8 12", 0b0000_0000u8, u8);
}
#[test]
@ -3567,7 +3584,7 @@ fn to_float_f64() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
// https://github.com/rtfeldman/roc/issues/2696
// https://github.com/roc-lang/roc/issues/2696
fn upcast_of_int_is_zext() {
assert_evals_to!(
indoc!(
@ -3582,7 +3599,7 @@ fn upcast_of_int_is_zext() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
// https://github.com/rtfeldman/roc/issues/2696
// https://github.com/roc-lang/roc/issues/2696
fn upcast_of_int_checked_is_zext() {
assert_evals_to!(
indoc!(

View file

@ -9,6 +9,8 @@ use crate::helpers::wasm::assert_evals_to;
use indoc::indoc;
#[allow(unused_imports)]
use roc_std::RocList;
#[allow(unused_imports)]
use roc_std::RocStr;
#[test]
@ -1232,8 +1234,8 @@ fn return_wrapped_closure() {
main = foo
"#
),
[5],
[i64; 1]
5,
i64
);
}
@ -2643,12 +2645,12 @@ fn pattern_match_unit_tag() {
);
}
// see for why this is disabled on wasm32 https://github.com/rtfeldman/roc/issues/1687
// see for why this is disabled on wasm32 https://github.com/roc-lang/roc/issues/1687
#[cfg(not(feature = "gen-llvm-wasm"))]
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn mirror_llvm_alignment_padding() {
// see https://github.com/rtfeldman/roc/issues/1569
// see https://github.com/roc-lang/roc/issues/1569
assert_evals_to!(
indoc!(
r#"
@ -2781,7 +2783,7 @@ fn lambda_set_enum_byte_byte() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_until() {
// see https://github.com/rtfeldman/roc/issues/1576
// see https://github.com/roc-lang/roc/issues/1576
assert_evals_to!(
indoc!(
r#"
@ -2807,7 +2809,7 @@ fn list_walk_until() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn int_literal_not_specialized_with_annotation() {
// see https://github.com/rtfeldman/roc/issues/1600
// see https://github.com/roc-lang/roc/issues/1600
assert_evals_to!(
indoc!(
r#"
@ -2835,7 +2837,7 @@ fn int_literal_not_specialized_with_annotation() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn int_literal_not_specialized_no_annotation() {
// see https://github.com/rtfeldman/roc/issues/1600
// see https://github.com/roc-lang/roc/issues/1600
assert_evals_to!(
indoc!(
r#"
@ -2862,7 +2864,7 @@ fn int_literal_not_specialized_no_annotation() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn unresolved_tvar_when_capture_is_unused() {
// see https://github.com/rtfeldman/roc/issues/1585
// see https://github.com/roc-lang/roc/issues/1585
assert_evals_to!(
indoc!(
r#"
@ -2908,7 +2910,7 @@ fn value_not_exposed_hits_panic() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn mix_function_and_closure() {
// see https://github.com/rtfeldman/roc/pull/1706
// see https://github.com/roc-lang/roc/pull/1706
assert_evals_to!(
indoc!(
r#"
@ -2934,7 +2936,7 @@ fn mix_function_and_closure() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn mix_function_and_closure_level_of_indirection() {
// see https://github.com/rtfeldman/roc/pull/1706
// see https://github.com/roc-lang/roc/pull/1706
assert_evals_to!(
indoc!(
r#"
@ -2960,7 +2962,7 @@ fn mix_function_and_closure_level_of_indirection() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg_attr(debug_assertions, ignore)] // this test stack-overflows the compiler in debug mode
fn do_pass_bool_byte_closure_layout() {
// see https://github.com/rtfeldman/roc/pull/1706
// see https://github.com/roc-lang/roc/pull/1706
// the distinction is actually important, dropping that info means some functions just get
// skipped
assert_evals_to!(
@ -3422,7 +3424,7 @@ fn polymorphic_lambda_set_multiple_specializations() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map2_conslist() {
// this had an RC problem, https://github.com/rtfeldman/roc/issues/2968
// this had an RC problem, https://github.com/roc-lang/roc/issues/2968
assert_evals_to!(
indoc!(
r#"
@ -3724,7 +3726,7 @@ fn recursive_lambda_set_issue_3444() {
),
RocStr::from("c"),
RocStr
);
)
}
#[test]
@ -3838,3 +3840,222 @@ fn compose_recursive_lambda_set_productive_inferred() {
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn local_binding_aliases_function() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
f : {} -> List a
f = \_ -> []
main : List U8
main =
g = f
g {}
"#
),
RocList::<u8>::from_slice(&[]),
RocList<u8>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn local_binding_aliases_function_inferred() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
f = \_ -> []
main =
g = f
g {}
"#
),
RocList::from_slice(&[]),
RocList<std::convert::Infallible>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn transient_captures() {
assert_evals_to!(
indoc!(
r#"
x = "abc"
getX = \{} -> x
h = \{} -> getX {}
h {}
"#
),
RocStr::from("abc"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn transient_captures_after_def_ordering() {
assert_evals_to!(
indoc!(
r#"
h = \{} -> getX {}
getX = \{} -> x
x = "abc"
h {}
"#
),
RocStr::from("abc"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn deep_transient_capture_chain() {
assert_evals_to!(
indoc!(
r#"
z = "abc"
getX = \{} -> getY {}
getY = \{} -> getZ {}
getZ = \{} -> z
h = \{} -> getX {}
h {}
"#
),
RocStr::from("abc"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn deep_transient_capture_chain_with_multiple_captures() {
assert_evals_to!(
indoc!(
r#"
h = "h"
x = "x"
y = "y"
z = "z"
getX = \{} -> Str.concat x (getY {})
getY = \{} -> Str.concat y (getZ {})
getZ = \{} -> z
getH = \{} -> Str.concat h (getX {})
getH {}
"#
),
RocStr::from("hxyz"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn transient_captures_from_outer_scope() {
assert_evals_to!(
indoc!(
r#"
x = "abc"
getX = \{} -> x
innerScope =
h = \{} -> getX {}
h {}
innerScope
"#
),
RocStr::from("abc"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn mutually_recursive_captures() {
assert_evals_to!(
indoc!(
r#"
x : Bool
x = True
y : Bool
y = False
a = "foo"
b = "bar"
foo = \{} -> if x then a else bar {}
bar = \{} -> if y then b else foo {}
bar {}
"#
),
RocStr::from("foo"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn monomorphization_sees_polymorphic_recursion() {
assert_evals_to!(
indoc!(
r#"
foo : a, Bool -> Str
foo = \in, b -> if b then "done" else bar in
bar = \_ -> foo {} True
foo "" False
"#
),
RocStr::from("done"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn int_let_generalization() {
assert_evals_to!(
indoc!(
r#"
manyAux : {} -> I32
manyAux = \_ ->
output = \_ -> 42
output {}
when manyAux {} is
_ -> "done"
"#
),
RocStr::from("done"),
RocStr
);
}

View file

@ -964,7 +964,7 @@ fn update_the_only_field() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
// https://github.com/rtfeldman/roc/issues/1513
// https://github.com/roc-lang/roc/issues/1513
fn both_have_unique_fields() {
assert_evals_to!(
indoc!(
@ -985,7 +985,7 @@ fn both_have_unique_fields() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
// https://github.com/rtfeldman/roc/issues/2535
// https://github.com/roc-lang/roc/issues/2535
fn different_proc_types_specialized_to_same_layout() {
assert_evals_to!(
indoc!(

View file

@ -1154,7 +1154,7 @@ fn applied_tag_function_result() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore = "This test has incorrect refcounts: https://github.com/rtfeldman/roc/issues/2968"]
#[ignore = "This test has incorrect refcounts: https://github.com/roc-lang/roc/issues/2968"]
fn applied_tag_function_linked_list() {
assert_evals_to!(
indoc!(
@ -1469,7 +1469,7 @@ fn issue_2458() {
}
#[test]
#[ignore = "See https://github.com/rtfeldman/roc/issues/2466"]
#[ignore = "See https://github.com/roc-lang/roc/issues/2466"]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_2458_deep_recursion_var() {
assert_evals_to!(
@ -1921,10 +1921,10 @@ fn issue_2165_recursive_tag_destructure() {
indoc!(
r#"
SomeTag : [ Ctor { rec : List SomeTag } ]
x : SomeTag
x = Ctor { rec: [] }
when x is
Ctor { rec } -> Num.toStr (List.len rec)
"#
@ -1933,3 +1933,25 @@ fn issue_2165_recursive_tag_destructure() {
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn tag_union_let_generalization() {
assert_evals_to!(
indoc!(
r#"
manyAux : {} -> [ Loop, Done ]
manyAux = \_ ->
output = Done
output
when manyAux {} is
Loop -> "loop"
Done -> "done"
"#
),
RocStr::from("done"),
RocStr
);
}

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
// we actually want to compare against the literal float bits
#![allow(clippy::float_cmp)]

View file

@ -20,4 +20,4 @@ roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" }
test_mono_macros = { path = "../test_mono_macros" }
bumpalo = { version = "3.8.0", features = ["collections"] }
indoc = "1.0.3"
indoc = "1.0.7"

View file

@ -4,17 +4,15 @@ procedure List.6 (#Attr.2):
procedure Test.1 (Test.5):
let Test.2 : I64 = 41i64;
let Test.10 : {I64} = Struct {Test.2};
let Test.9 : List {I64} = Array [Test.10];
let Test.9 : List I64 = Array [Test.2];
ret Test.9;
procedure Test.3 (Test.8, #Attr.12):
let Test.2 : I64 = StructAtIndex 0 #Attr.12;
procedure Test.3 (Test.8, Test.2):
ret Test.2;
procedure Test.0 ():
let Test.7 : {} = Struct {};
let Test.4 : List {I64} = CallByName Test.1 Test.7;
let Test.4 : List I64 = CallByName Test.1 Test.7;
let Test.6 : U64 = CallByName List.6 Test.4;
dec Test.4;
ret Test.6;

View file

@ -12,22 +12,20 @@ procedure List.71 (#Attr.2, #Attr.3):
let List.388 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.388;
procedure Test.23 (Test.24, Test.35, #Attr.12):
let Test.22 : U8 = StructAtIndex 0 #Attr.12;
procedure Test.23 (Test.24, Test.35, Test.22):
let Test.37 : List U8 = CallByName List.4 Test.24 Test.22;
ret Test.37;
procedure Test.8 (Test.22):
let Test.34 : {U8} = Struct {Test.22};
ret Test.34;
ret Test.22;
procedure Test.9 (Test.27):
let Test.33 : {U8} = CallByName Test.8 Test.27;
let Test.33 : U8 = CallByName Test.8 Test.27;
ret Test.33;
procedure Test.0 ():
let Test.32 : U8 = 15i64;
let Test.28 : {U8} = CallByName Test.9 Test.32;
let Test.28 : U8 = CallByName Test.9 Test.32;
let Test.30 : List U8 = Array [];
let Test.31 : {} = Struct {};
let Test.29 : List U8 = CallByName Test.23 Test.30 Test.31 Test.28;

View file

@ -1,34 +1,26 @@
procedure #Derived.0 (#Derived.1):
let #Derived_gen.1 : {Str} = Struct {#Derived.1};
let #Derived_gen.0 : {Str} = CallByName Encode.22 #Derived_gen.1;
let #Derived_gen.0 : Str = CallByName Encode.22 #Derived.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;
procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1):
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.20 #Derived_gen.5;
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.20 #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;
let #Derived_gen.14 : Str = CallByName Encode.22 #Derived.6;
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;
procedure #Derived.7 (#Derived.8, #Derived.9, #Derived.6):
let #Derived_gen.21 : Str = "b";
let #Derived_gen.22 : {Str} = CallByName Json.18 #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.20 #Derived_gen.19;
let #Derived_gen.22 : Str = CallByName Json.18 #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.20 #Derived_gen.19;
let #Derived_gen.17 : List U8 = CallByName Encode.23 #Derived.8 #Derived_gen.18 #Derived.9;
ret #Derived_gen.17;
@ -69,7 +61,7 @@ procedure Encode.23 (Encode.94, Encode.102, Encode.96):
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {Str} = CallByName #Derived.0 Encode.100;
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;
@ -77,10 +69,7 @@ procedure Json.1 ():
let Json.318 : {} = Struct {};
ret Json.318;
procedure Json.103 (Json.104, Json.321, #Attr.12):
let Json.102 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.102;
dec #Attr.12;
procedure Json.103 (Json.104, Json.321, Json.102):
let Json.354 : I32 = 123i64;
let Json.353 : U8 = CallByName Num.123 Json.354;
let Json.106 : List U8 = CallByName List.4 Json.104 Json.353;
@ -97,10 +86,7 @@ procedure Json.103 (Json.104, Json.321, #Attr.12):
let Json.325 : List U8 = CallByName List.4 Json.108 Json.326;
ret Json.325;
procedure Json.103 (Json.104, Json.321, #Attr.12):
let Json.102 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.102;
dec #Attr.12;
procedure Json.103 (Json.104, Json.321, Json.102):
let Json.397 : I32 = 123i64;
let Json.396 : U8 = CallByName Num.123 Json.397;
let Json.106 : List U8 = CallByName List.4 Json.104 Json.396;
@ -120,7 +106,7 @@ procedure Json.103 (Json.104, Json.321, #Attr.12):
procedure Json.105 (Json.323, Json.324):
let Json.111 : Str = StructAtIndex 0 Json.324;
inc Json.111;
let Json.112 : {Str} = StructAtIndex 1 Json.324;
let Json.112 : Str = StructAtIndex 1 Json.324;
inc Json.112;
dec Json.324;
let Json.109 : List U8 = StructAtIndex 0 Json.323;
@ -159,7 +145,7 @@ procedure Json.105 (Json.323, Json.324):
procedure Json.105 (Json.323, Json.324):
let Json.111 : Str = StructAtIndex 0 Json.324;
inc Json.111;
let Json.112 : {Str} = StructAtIndex 1 Json.324;
let Json.112 : Str = StructAtIndex 1 Json.324;
inc Json.112;
dec Json.324;
let Json.109 : List U8 = StructAtIndex 0 Json.323;
@ -196,24 +182,18 @@ procedure Json.105 (Json.323, Json.324):
jump Json.378 Json.113;
procedure Json.18 (Json.86):
let Json.365 : {Str} = Struct {Json.86};
let Json.364 : {Str} = CallByName Encode.22 Json.365;
let Json.364 : Str = CallByName Encode.22 Json.86;
ret Json.364;
procedure Json.20 (Json.102):
let Json.320 : {List {Str, {Str}}} = Struct {Json.102};
let Json.319 : {List {Str, {Str}}} = CallByName Encode.22 Json.320;
let Json.319 : List {Str, Str} = CallByName Encode.22 Json.102;
ret Json.319;
procedure Json.20 (Json.102):
let Json.362 : {List {Str, {Str}}} = Struct {Json.102};
let Json.361 : {List {Str, {Str}}} = CallByName Encode.22 Json.362;
let Json.361 : List {Str, Str} = CallByName Encode.22 Json.102;
ret Json.361;
procedure Json.87 (Json.88, Json.366, #Attr.12):
let Json.86 : Str = StructAtIndex 0 #Attr.12;
inc Json.86;
dec #Attr.12;
procedure Json.87 (Json.88, Json.366, Json.86):
let Json.406 : I32 = 34i64;
let Json.405 : U8 = CallByName Num.123 Json.406;
let Json.403 : List U8 = CallByName List.4 Json.88 Json.405;
@ -224,21 +204,18 @@ procedure Json.87 (Json.88, Json.366, #Attr.12):
let Json.399 : List U8 = CallByName List.4 Json.400 Json.401;
ret Json.399;
procedure List.133 (List.134, List.135, #Attr.12):
let List.132 : {} = StructAtIndex 0 #Attr.12;
procedure List.133 (List.134, List.135, List.132):
let List.434 : {List U8, U64} = CallByName Json.105 List.134 List.135;
let List.433 : [C [], C {List U8, U64}] = TagId(1) List.434;
ret List.433;
procedure List.133 (List.134, List.135, #Attr.12):
let List.132 : {} = StructAtIndex 0 #Attr.12;
procedure List.133 (List.134, List.135, List.132):
let List.515 : {List U8, U64} = CallByName Json.105 List.134 List.135;
let List.514 : [C [], C {List U8, U64}] = TagId(1) List.515;
ret List.514;
procedure List.18 (List.130, List.131, List.132):
let List.411 : {{}} = Struct {List.132};
let List.405 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.411;
let List.405 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.132;
let List.408 : U8 = 1i64;
let List.409 : U8 = GetTagId List.405;
let List.410 : Int1 = lowlevel Eq List.408 List.409;
@ -254,8 +231,7 @@ procedure List.18 (List.130, List.131, List.132):
ret List.407;
procedure List.18 (List.130, List.131, List.132):
let List.491 : {{}} = Struct {List.132};
let List.485 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.491;
let List.485 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.132;
let List.488 : U8 = 1i64;
let List.489 : U8 = GetTagId List.485;
let List.490 : Int1 = lowlevel Eq List.488 List.489;
@ -289,11 +265,11 @@ procedure List.6 (#Attr.2):
ret List.494;
procedure List.66 (#Attr.2, #Attr.3):
let List.432 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let List.432 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.432;
procedure List.66 (#Attr.2, #Attr.3):
let List.513 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let List.513 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.513;
procedure List.69 (#Attr.2):
@ -328,7 +304,7 @@ procedure List.86 (List.448, List.449, List.450, List.451, List.452):
joinpoint List.420 List.364 List.365 List.366 List.367 List.368:
let List.422 : Int1 = CallByName Num.22 List.367 List.368;
if List.422 then
let List.431 : {Str, {Str}} = CallByName List.66 List.364 List.367;
let List.431 : {Str, Str} = CallByName List.66 List.364 List.367;
let List.423 : [C [], C {List U8, U64}] = CallByName List.133 List.365 List.431 List.366;
let List.428 : U8 = 1i64;
let List.429 : U8 = GetTagId List.423;
@ -355,7 +331,7 @@ procedure List.86 (List.529, List.530, List.531, List.532, List.533):
joinpoint List.501 List.364 List.365 List.366 List.367 List.368:
let List.503 : Int1 = CallByName Num.22 List.367 List.368;
if List.503 then
let List.512 : {Str, {Str}} = CallByName List.66 List.364 List.367;
let List.512 : {Str, Str} = CallByName List.66 List.364 List.367;
let List.504 : [C [], C {List U8, U64}] = CallByName List.133 List.365 List.512 List.366;
let List.509 : U8 = 1i64;
let List.510 : U8 = GetTagId List.504;
@ -399,31 +375,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.285;
procedure Str.12 (#Attr.2):
let Str.212 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.212;
let Str.219 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.219;
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;
let Str.211 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.211;
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;
let Str.209 : U64 = 0i64;
let Str.210 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.209 Str.210;
let Str.206 : Int1 = StructAtIndex 2 Str.70;
if Str.206 then
let Str.208 : Str = StructAtIndex 1 Str.70;
inc Str.208;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
let Str.207 : [C {U64, U8}, C Str] = TagId(1) Str.208;
ret Str.207;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
let Str.204 : U8 = StructAtIndex 3 Str.70;
let Str.205 : 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;
let Str.203 : {U64, U8} = Struct {Str.205, Str.204};
let Str.202 : [C {U64, U8}, C Str] = TagId(0) Str.203;
ret Str.202;
procedure Test.0 ():
let Test.12 : Str = "bar";

View file

@ -1,17 +1,13 @@
procedure #Derived.0 (#Derived.1):
let #Derived_gen.1 : {Str} = Struct {#Derived.1};
let #Derived_gen.0 : {Str} = CallByName Encode.22 #Derived_gen.1;
let #Derived_gen.0 : Str = CallByName Encode.22 #Derived.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;
procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1):
let #Derived_gen.7 : Str = "a";
let #Derived_gen.8 : {Str} = CallByName Json.18 #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.20 #Derived_gen.5;
let #Derived_gen.8 : Str = CallByName Json.18 #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.20 #Derived_gen.5;
let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4;
ret #Derived_gen.3;
@ -38,7 +34,7 @@ procedure Encode.23 (Encode.94, Encode.102, Encode.96):
procedure Encode.25 (Encode.100, Encode.101):
let Encode.104 : List U8 = Array [];
let Encode.105 : {Str} = CallByName #Derived.0 Encode.100;
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;
@ -46,10 +42,7 @@ procedure Json.1 ():
let Json.318 : {} = Struct {};
ret Json.318;
procedure Json.103 (Json.104, Json.321, #Attr.12):
let Json.102 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.102;
dec #Attr.12;
procedure Json.103 (Json.104, Json.321, Json.102):
let Json.357 : I32 = 123i64;
let Json.356 : U8 = CallByName Num.123 Json.357;
let Json.106 : List U8 = CallByName List.4 Json.104 Json.356;
@ -69,7 +62,7 @@ procedure Json.103 (Json.104, Json.321, #Attr.12):
procedure Json.105 (Json.326, Json.327):
let Json.111 : Str = StructAtIndex 0 Json.327;
inc Json.111;
let Json.112 : {Str} = StructAtIndex 1 Json.327;
let Json.112 : Str = StructAtIndex 1 Json.327;
inc Json.112;
dec Json.327;
let Json.109 : List U8 = StructAtIndex 0 Json.326;
@ -106,19 +99,14 @@ procedure Json.105 (Json.326, Json.327):
jump Json.338 Json.113;
procedure Json.18 (Json.86):
let Json.323 : {Str} = Struct {Json.86};
let Json.322 : {Str} = CallByName Encode.22 Json.323;
let Json.322 : Str = CallByName Encode.22 Json.86;
ret Json.322;
procedure Json.20 (Json.102):
let Json.320 : {List {Str, {Str}}} = Struct {Json.102};
let Json.319 : {List {Str, {Str}}} = CallByName Encode.22 Json.320;
let Json.319 : List {Str, Str} = CallByName Encode.22 Json.102;
ret Json.319;
procedure Json.87 (Json.88, Json.324, #Attr.12):
let Json.86 : Str = StructAtIndex 0 #Attr.12;
inc Json.86;
dec #Attr.12;
procedure Json.87 (Json.88, Json.324, Json.86):
let Json.366 : I32 = 34i64;
let Json.365 : U8 = CallByName Num.123 Json.366;
let Json.363 : List U8 = CallByName List.4 Json.88 Json.365;
@ -129,15 +117,13 @@ procedure Json.87 (Json.88, Json.324, #Attr.12):
let Json.359 : List U8 = CallByName List.4 Json.360 Json.361;
ret Json.359;
procedure List.133 (List.134, List.135, #Attr.12):
let List.132 : {} = StructAtIndex 0 #Attr.12;
procedure List.133 (List.134, List.135, List.132):
let List.441 : {List U8, U64} = CallByName Json.105 List.134 List.135;
let List.440 : [C [], C {List U8, U64}] = TagId(1) List.441;
ret List.440;
procedure List.18 (List.130, List.131, List.132):
let List.417 : {{}} = Struct {List.132};
let List.411 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.417;
let List.411 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.132;
let List.414 : U8 = 1i64;
let List.415 : U8 = GetTagId List.411;
let List.416 : Int1 = lowlevel Eq List.414 List.415;
@ -167,7 +153,7 @@ procedure List.6 (#Attr.2):
ret List.420;
procedure List.66 (#Attr.2, #Attr.3):
let List.439 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let List.439 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.439;
procedure List.69 (#Attr.2):
@ -196,7 +182,7 @@ procedure List.86 (List.455, List.456, List.457, List.458, List.459):
joinpoint List.427 List.364 List.365 List.366 List.367 List.368:
let List.429 : Int1 = CallByName Num.22 List.367 List.368;
if List.429 then
let List.438 : {Str, {Str}} = CallByName List.66 List.364 List.367;
let List.438 : {Str, Str} = CallByName List.66 List.364 List.367;
let List.430 : [C [], C {List U8, U64}] = CallByName List.133 List.365 List.438 List.366;
let List.435 : U8 = 1i64;
let List.436 : U8 = GetTagId List.430;
@ -240,31 +226,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.266;
procedure Str.12 (#Attr.2):
let Str.210 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.210;
let Str.217 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.217;
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;
let Str.211 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.211;
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;
let Str.209 : U64 = 0i64;
let Str.210 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.209 Str.210;
let Str.206 : Int1 = StructAtIndex 2 Str.70;
if Str.206 then
let Str.208 : Str = StructAtIndex 1 Str.70;
inc Str.208;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
let Str.207 : [C {U64, U8}, C Str] = TagId(1) Str.208;
ret Str.207;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
let Str.204 : U8 = StructAtIndex 3 Str.70;
let Str.205 : 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;
let Str.203 : {U64, U8} = Struct {Str.205, Str.204};
let Str.202 : [C {U64, U8}, C Str] = TagId(0) Str.203;
ret Str.202;
procedure Test.0 ():
let Test.11 : Str = "foo";

View file

@ -1,25 +1,21 @@
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;
let #Derived_gen.0 : {Str, Str} = CallByName Encode.22 #Derived.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;
procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1):
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.18 #Derived_gen.13;
let #Derived_gen.6 : {Str, {Str}} = Struct {#Derived_gen.11, #Derived_gen.12};
let #Derived_gen.12 : Str = CallByName Json.18 #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.18 #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.20 #Derived_gen.5;
let #Derived_gen.9 : Str = CallByName Json.18 #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.20 #Derived_gen.5;
let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4;
ret #Derived_gen.3;
@ -46,7 +42,7 @@ procedure Encode.23 (Encode.94, Encode.102, Encode.96):
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.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;
@ -54,10 +50,7 @@ procedure Json.1 ():
let Json.318 : {} = Struct {};
ret Json.318;
procedure Json.103 (Json.104, Json.321, #Attr.12):
let Json.102 : List {Str, {Str}} = StructAtIndex 0 #Attr.12;
inc Json.102;
dec #Attr.12;
procedure Json.103 (Json.104, Json.321, Json.102):
let Json.360 : I32 = 123i64;
let Json.359 : U8 = CallByName Num.123 Json.360;
let Json.106 : List U8 = CallByName List.4 Json.104 Json.359;
@ -77,7 +70,7 @@ procedure Json.103 (Json.104, Json.321, #Attr.12):
procedure Json.105 (Json.329, Json.330):
let Json.111 : Str = StructAtIndex 0 Json.330;
inc Json.111;
let Json.112 : {Str} = StructAtIndex 1 Json.330;
let Json.112 : Str = StructAtIndex 1 Json.330;
inc Json.112;
dec Json.330;
let Json.109 : List U8 = StructAtIndex 0 Json.329;
@ -114,19 +107,14 @@ procedure Json.105 (Json.329, Json.330):
jump Json.341 Json.113;
procedure Json.18 (Json.86):
let Json.326 : {Str} = Struct {Json.86};
let Json.325 : {Str} = CallByName Encode.22 Json.326;
let Json.325 : Str = CallByName Encode.22 Json.86;
ret Json.325;
procedure Json.20 (Json.102):
let Json.320 : {List {Str, {Str}}} = Struct {Json.102};
let Json.319 : {List {Str, {Str}}} = CallByName Encode.22 Json.320;
let Json.319 : List {Str, Str} = CallByName Encode.22 Json.102;
ret Json.319;
procedure Json.87 (Json.88, Json.324, #Attr.12):
let Json.86 : Str = StructAtIndex 0 #Attr.12;
inc Json.86;
dec #Attr.12;
procedure Json.87 (Json.88, Json.324, Json.86):
let Json.369 : I32 = 34i64;
let Json.368 : U8 = CallByName Num.123 Json.369;
let Json.366 : List U8 = CallByName List.4 Json.88 Json.368;
@ -137,15 +125,13 @@ procedure Json.87 (Json.88, Json.324, #Attr.12):
let Json.362 : List U8 = CallByName List.4 Json.363 Json.364;
ret Json.362;
procedure List.133 (List.134, List.135, #Attr.12):
let List.132 : {} = StructAtIndex 0 #Attr.12;
procedure List.133 (List.134, List.135, List.132):
let List.441 : {List U8, U64} = CallByName Json.105 List.134 List.135;
let List.440 : [C [], C {List U8, U64}] = TagId(1) List.441;
ret List.440;
procedure List.18 (List.130, List.131, List.132):
let List.417 : {{}} = Struct {List.132};
let List.411 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.417;
let List.411 : [C [], C {List U8, U64}] = CallByName List.75 List.130 List.131 List.132;
let List.414 : U8 = 1i64;
let List.415 : U8 = GetTagId List.411;
let List.416 : Int1 = lowlevel Eq List.414 List.415;
@ -175,7 +161,7 @@ procedure List.6 (#Attr.2):
ret List.420;
procedure List.66 (#Attr.2, #Attr.3):
let List.439 : {Str, {Str}} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let List.439 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.439;
procedure List.69 (#Attr.2):
@ -204,7 +190,7 @@ procedure List.86 (List.455, List.456, List.457, List.458, List.459):
joinpoint List.427 List.364 List.365 List.366 List.367 List.368:
let List.429 : Int1 = CallByName Num.22 List.367 List.368;
if List.429 then
let List.438 : {Str, {Str}} = CallByName List.66 List.364 List.367;
let List.438 : {Str, Str} = CallByName List.66 List.364 List.367;
let List.430 : [C [], C {List U8, U64}] = CallByName List.133 List.365 List.438 List.366;
let List.435 : U8 = 1i64;
let List.436 : U8 = GetTagId List.430;
@ -248,31 +234,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.266;
procedure Str.12 (#Attr.2):
let Str.210 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.210;
let Str.217 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.217;
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;
let Str.211 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.211;
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;
let Str.209 : U64 = 0i64;
let Str.210 : U64 = CallByName List.6 Str.69;
let Str.70 : {U64, Str, Int1, U8} = CallByName Str.48 Str.69 Str.209 Str.210;
let Str.206 : Int1 = StructAtIndex 2 Str.70;
if Str.206 then
let Str.208 : Str = StructAtIndex 1 Str.70;
inc Str.208;
dec Str.70;
let Str.200 : [C {U64, U8}, C Str] = TagId(1) Str.201;
ret Str.200;
let Str.207 : [C {U64, U8}, C Str] = TagId(1) Str.208;
ret Str.207;
else
let Str.197 : U8 = StructAtIndex 3 Str.70;
let Str.198 : U64 = StructAtIndex 0 Str.70;
let Str.204 : U8 = StructAtIndex 3 Str.70;
let Str.205 : 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;
let Str.203 : {U64, U8} = Struct {Str.205, Str.204};
let Str.202 : [C {U64, U8}, C Str] = TagId(0) Str.203;
ret Str.202;
procedure Test.0 ():
let Test.11 : Str = "foo";

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