Merge branch 'trunk' into 3378

This commit is contained in:
Ayaz 2022-07-06 20:37:32 -05:00 committed by GitHub
commit 22f02984d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 1740 additions and 813 deletions

View file

@ -1100,7 +1100,7 @@ fn link_windows(
todo!("Add windows support to the surgical linker. See issue #2608.")
}
pub fn module_to_dylib(
pub fn llvm_module_to_dylib(
module: &inkwell::module::Module,
target: &Triple,
opt_level: OptLevel,

View file

@ -1,24 +1,22 @@
interface Dict
exposes
[
empty,
single,
get,
walk,
insert,
len,
remove,
contains,
keys,
values,
union,
intersection,
difference,
]
imports
[
Bool.{ Bool },
]
exposes [
empty,
single,
get,
walk,
insert,
len,
remove,
contains,
keys,
values,
union,
intersection,
difference,
]
imports [
Bool.{ Bool },
]
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
##

View file

@ -1,35 +1,33 @@
interface Encode
exposes
[
Encoder,
Encoding,
toEncoder,
EncoderFormatting,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tag,
custom,
appendWith,
append,
toBytes,
]
imports
[]
exposes [
Encoder,
Encoding,
toEncoder,
EncoderFormatting,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tag,
custom,
appendWith,
append,
toBytes,
]
imports []
Encoder fmt := List U8, fmt -> List U8 | fmt has EncoderFormatting

View file

@ -1,35 +1,33 @@
interface Json
exposes
[
Json,
format,
]
imports
[
Encode.{
Encoder,
custom,
appendWith,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tag,
},
]
exposes [
Json,
format,
]
imports [
Encode.{
Encoder,
custom,
appendWith,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tag,
},
]
Json := {}

View file

@ -1,63 +1,61 @@
interface List
exposes
[
isEmpty,
get,
set,
replace,
append,
map,
len,
withCapacity,
iterate,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
prepend,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
keepErrs,
mapWithIndex,
map2,
map3,
product,
walkUntil,
range,
sortWith,
drop,
swap,
dropAt,
dropLast,
min,
max,
map4,
dropFirst,
joinMap,
any,
takeFirst,
takeLast,
find,
findIndex,
sublist,
intersperse,
split,
all,
dropIf,
sortAsc,
sortDesc,
]
imports
[
Bool.{ Bool },
]
exposes [
isEmpty,
get,
set,
replace,
append,
map,
len,
withCapacity,
iterate,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
prepend,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
keepErrs,
mapWithIndex,
map2,
map3,
product,
walkUntil,
range,
sortWith,
drop,
swap,
dropAt,
dropLast,
min,
max,
map4,
dropFirst,
joinMap,
any,
takeFirst,
takeLast,
find,
findIndex,
sublist,
intersperse,
split,
all,
dropIf,
sortAsc,
sortDesc,
]
imports [
Bool.{ Bool },
]
## Types
## A sequential list of values.
@ -373,17 +371,11 @@ contains = \list, needle ->
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
walk = \list, state, func ->
walkHelp list state func 0 (len list)
walkHelp = \currentState, element -> Continue (func currentState element)
## internal helper
walkHelp : List elem, state, (state, elem -> state), Nat, Nat -> state
walkHelp = \list, state, f, index, length ->
if index < length then
nextState = f state (getUnsafe list index)
walkHelp list nextState f (index + 1) length
else
state
when List.iterate list state walkHelp is
Continue newState -> newState
Break void -> List.unreachable void
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
@ -841,3 +833,6 @@ iterHelp = \list, state, f, index, length ->
Break b
else
Continue state
## useful for typechecking guaranteed-unreachable cases
unreachable : [] -> a

View file

@ -1,153 +1,151 @@
interface Num
exposes
[
Num,
Int,
Frac,
Integer,
FloatingPoint,
I128,
I64,
I32,
I16,
I8,
U128,
U64,
U32,
U16,
U8,
Signed128,
Signed64,
Signed32,
Signed16,
Signed8,
Unsigned128,
Unsigned64,
Unsigned32,
Unsigned16,
Unsigned8,
Nat,
Dec,
F32,
F64,
Natural,
Decimal,
Binary32,
Binary64,
abs,
neg,
add,
sub,
mul,
isLt,
isLte,
isGt,
isGte,
sin,
cos,
tan,
atan,
acos,
asin,
isZero,
isEven,
isOdd,
toFrac,
isPositive,
isNegative,
rem,
remChecked,
div,
divChecked,
sqrt,
sqrtChecked,
log,
logChecked,
round,
ceiling,
floor,
compare,
pow,
powInt,
addWrap,
addChecked,
addSaturated,
bitwiseAnd,
bitwiseXor,
bitwiseOr,
shiftLeftBy,
shiftRightBy,
shiftRightZfBy,
subWrap,
subChecked,
subSaturated,
mulWrap,
mulSaturated,
mulChecked,
intCast,
bytesToU16,
bytesToU32,
divCeil,
divCeilChecked,
divTrunc,
divTruncChecked,
toStr,
isMultipleOf,
minI8,
maxI8,
minU8,
maxU8,
minI16,
maxI16,
minU16,
maxU16,
minI32,
maxI32,
minU32,
maxU32,
minI64,
maxI64,
minU64,
maxU64,
minI128,
maxI128,
minU128,
maxU128,
minF32,
maxF32,
minF64,
maxF64,
toI8,
toI8Checked,
toI16,
toI16Checked,
toI32,
toI32Checked,
toI64,
toI64Checked,
toI128,
toI128Checked,
toU8,
toU8Checked,
toU16,
toU16Checked,
toU32,
toU32Checked,
toU64,
toU64Checked,
toU128,
toU128Checked,
toNat,
toNatChecked,
toF32,
toF32Checked,
toF64,
toF64Checked,
]
imports
[
Bool.{ Bool },
]
exposes [
Num,
Int,
Frac,
Integer,
FloatingPoint,
I128,
I64,
I32,
I16,
I8,
U128,
U64,
U32,
U16,
U8,
Signed128,
Signed64,
Signed32,
Signed16,
Signed8,
Unsigned128,
Unsigned64,
Unsigned32,
Unsigned16,
Unsigned8,
Nat,
Dec,
F32,
F64,
Natural,
Decimal,
Binary32,
Binary64,
abs,
neg,
add,
sub,
mul,
isLt,
isLte,
isGt,
isGte,
sin,
cos,
tan,
atan,
acos,
asin,
isZero,
isEven,
isOdd,
toFrac,
isPositive,
isNegative,
rem,
remChecked,
div,
divChecked,
sqrt,
sqrtChecked,
log,
logChecked,
round,
ceiling,
floor,
compare,
pow,
powInt,
addWrap,
addChecked,
addSaturated,
bitwiseAnd,
bitwiseXor,
bitwiseOr,
shiftLeftBy,
shiftRightBy,
shiftRightZfBy,
subWrap,
subChecked,
subSaturated,
mulWrap,
mulSaturated,
mulChecked,
intCast,
bytesToU16,
bytesToU32,
divCeil,
divCeilChecked,
divTrunc,
divTruncChecked,
toStr,
isMultipleOf,
minI8,
maxI8,
minU8,
maxU8,
minI16,
maxI16,
minU16,
maxU16,
minI32,
maxI32,
minU32,
maxU32,
minI64,
maxI64,
minU64,
maxU64,
minI128,
maxI128,
minU128,
maxU128,
minF32,
maxF32,
minF64,
maxF64,
toI8,
toI8Checked,
toI16,
toI16Checked,
toI32,
toI32Checked,
toI64,
toI64Checked,
toI128,
toI128Checked,
toU8,
toU8Checked,
toU16,
toU16Checked,
toU32,
toU32Checked,
toU64,
toU64Checked,
toU128,
toU128Checked,
toNat,
toNatChecked,
toF32,
toF32Checked,
toF64,
toF64Checked,
]
imports [
Bool.{ Bool },
]
## Represents a number that could be either an [Int] or a [Frac].
##

View file

@ -1,19 +1,18 @@
interface Set
exposes
[
empty,
single,
walk,
insert,
len,
remove,
contains,
toList,
fromList,
union,
intersection,
difference,
]
exposes [
empty,
single,
walk,
insert,
len,
remove,
contains,
toList,
fromList,
union,
intersection,
difference,
]
imports [List, Bool.{ Bool }, Dict.{ values }]
## An empty set.

View file

@ -1,47 +1,46 @@
interface Str
exposes
[
Utf8Problem,
Utf8ByteProblem,
concat,
isEmpty,
joinWith,
split,
repeat,
countGraphemes,
countUtf8Bytes,
startsWithScalar,
toUtf8,
fromUtf8,
fromUtf8Range,
startsWith,
endsWith,
trim,
trimLeft,
trimRight,
toDec,
toF64,
toF32,
toNat,
toU128,
toI128,
toU64,
toI64,
toU32,
toI32,
toU16,
toI16,
toU8,
toI8,
toScalars,
splitFirst,
splitLast,
walkUtf8WithIndex,
reserve,
appendScalar,
walkScalars,
walkScalarsUntil,
]
exposes [
Utf8Problem,
Utf8ByteProblem,
concat,
isEmpty,
joinWith,
split,
repeat,
countGraphemes,
countUtf8Bytes,
startsWithScalar,
toUtf8,
fromUtf8,
fromUtf8Range,
startsWith,
endsWith,
trim,
trimLeft,
trimRight,
toDec,
toF64,
toF32,
toNat,
toU128,
toI128,
toU64,
toI64,
toU32,
toI32,
toU16,
toI16,
toU8,
toI8,
toScalars,
splitFirst,
splitLast,
walkUtf8WithIndex,
reserve,
appendScalar,
walkScalars,
walkScalarsUntil,
]
imports [Bool.{ Bool }, Result.{ Result }]
## # Types
@ -118,15 +117,14 @@ interface Str
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
Utf8ByteProblem :
[
InvalidStartByte,
UnexpectedEndOfSequence,
ExpectedContinuation,
OverlongEncoding,
CodepointTooLarge,
EncodesSurrogateHalf,
]
Utf8ByteProblem : [
InvalidStartByte,
UnexpectedEndOfSequence,
ExpectedContinuation,
OverlongEncoding,
CodepointTooLarge,
EncodesSurrogateHalf,
]
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }

View file

@ -107,6 +107,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_TO_I16 => str_to_num,
STR_TO_U8 => str_to_num,
STR_TO_I8 => str_to_num,
LIST_UNREACHABLE => roc_unreachable,
LIST_LEN => list_len,
LIST_WITH_CAPACITY => list_with_capacity,
LIST_GET_UNSAFE => list_get_unsafe,
@ -2359,6 +2360,11 @@ fn list_prepend(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.unreachable : [] -> a
fn roc_unreachable(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::Unreachable, var_store)
}
/// List.map : List before, (before -> after) -> List after
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListMap, var_store)

View file

@ -104,6 +104,9 @@ flags! {
/// Prints debug information during the alias analysis pass.
ROC_DEBUG_ALIAS_ANALYSIS
/// Print to stderr when a runtime error function is generated.
ROC_PRINT_RUNTIME_ERROR_GEN
// ===LLVM Gen===
/// Prints LLVM function verification output.

View file

@ -74,7 +74,7 @@ impl<'a> Formattable for TypeDef<'a> {
buf.push_str(" :");
buf.spaces(1);
ann.format(buf, indent + INDENT)
ann.format(buf, indent)
}
Opaque {
header: TypeHeader { name, vars },
@ -113,7 +113,7 @@ impl<'a> Formattable for TypeDef<'a> {
buf.indent(indent + INDENT);
}
ann.format(buf, indent + INDENT);
ann.format(buf, indent);
if let Some(derived) = derived {
if !make_multiline {

View file

@ -2,7 +2,10 @@ use crate::annotation::{Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::def::fmt_defs;
use crate::pattern::fmt_pattern;
use crate::spaces::{count_leading_newlines, fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
use crate::spaces::{
count_leading_newlines, fmt_comments_only, fmt_spaces, fmt_spaces_no_blank_lines, NewlineAt,
INDENT,
};
use crate::Buf;
use roc_module::called_via::{self, BinOp};
use roc_parse::ast::{
@ -330,7 +333,7 @@ impl<'a> Formattable for Expr<'a> {
match &ret.value {
SpaceBefore(sub_expr, spaces) => {
let empty_line_before_return = empty_line_before_expr(&ret.value);
let has_inline_comment = with_inline_comment(&ret.value);
let has_inline_comment = has_line_comment_before(&ret.value);
if has_inline_comment {
buf.spaces(1);
@ -369,7 +372,7 @@ impl<'a> Formattable for Expr<'a> {
}
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List(items) => fmt_collection(buf, indent, Braces::Square, *items, Newlines::No),
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
BinOps(lefts, right) => fmt_binops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => {
buf.indent(indent);
match &unary_op.value {
@ -405,20 +408,7 @@ fn starts_with_newline(expr: &Expr) -> bool {
match expr {
SpaceBefore(_, comment_or_newline) => {
if !comment_or_newline.is_empty() {
// safe because we check the length before
comment_or_newline.get(0).unwrap().is_newline()
} else {
false
}
}
SpaceAfter(_, comment_or_newline) => {
if !(**comment_or_newline).is_empty() {
// safe because we check the length before
comment_or_newline.get(0).unwrap().is_newline()
} else {
false
}
matches!(comment_or_newline.first(), Some(CommentOrNewline::Newline))
}
_ => false,
}
@ -530,34 +520,34 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.push('"');
}
fn fmt_bin_ops<'a, 'buf>(
fn fmt_binops<'a, 'buf>(
buf: &mut Buf<'buf>,
lefts: &'a [(Loc<Expr<'a>>, Loc<BinOp>)],
loc_right_side: &'a Loc<Expr<'a>>,
part_of_multi_line_bin_ops: bool,
part_of_multi_line_binops: bool,
apply_needs_parens: Parens,
indent: u16,
) {
let is_multiline = part_of_multi_line_bin_ops
let is_multiline = part_of_multi_line_binops
|| (&loc_right_side.value).is_multiline()
|| lefts.iter().any(|(expr, _)| expr.value.is_multiline());
let mut curr_indent = indent;
for (loc_left_side, loc_bin_op) in lefts {
let bin_op = loc_bin_op.value;
for (loc_left_side, loc_binop) in lefts {
let binop = loc_binop.value;
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, curr_indent);
if is_multiline {
buf.newline();
buf.ensure_ends_in_newline();
curr_indent = indent + INDENT;
buf.indent(curr_indent);
} else {
buf.spaces(1);
}
push_op(buf, bin_op);
push_op(buf, binop);
buf.spaces(1);
}
@ -577,24 +567,23 @@ fn format_spaces<'a, 'buf>(
newlines: Newlines,
indent: u16,
) {
let format_newlines = newlines == Newlines::Yes;
if format_newlines {
fmt_spaces(buf, spaces.iter(), indent);
} else {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
match newlines {
Newlines::Yes => {
fmt_spaces(buf, spaces.iter(), indent);
}
Newlines::No => {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
}
}
fn with_inline_comment<'a>(expr: &'a Expr<'a>) -> bool {
fn has_line_comment_before<'a>(expr: &'a Expr<'a>) -> bool {
use roc_parse::ast::Expr::*;
match expr {
SpaceBefore(_, spaces) => match spaces.iter().next() {
Some(CommentOrNewline::LineComment(_)) => true,
Some(_) => false,
None => false,
},
SpaceBefore(_, spaces) => {
matches!(spaces.iter().next(), Some(CommentOrNewline::LineComment(_)))
}
_ => false,
}
}
@ -670,11 +659,22 @@ fn fmt_when<'a, 'buf>(
buf.newline();
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
// If any of the spaces is a newline, add a newline at the top.
// Otherwise leave it as just a comment.
let newline_at = if spaces_below_expr
.iter()
.any(|spaces| matches!(spaces, CommentOrNewline::Newline))
{
NewlineAt::Top
} else {
NewlineAt::None
};
expr_above.format(buf, condition_indent);
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
newline_at,
condition_indent,
);
buf.newline();
@ -699,25 +699,35 @@ fn fmt_when<'a, 'buf>(
buf.push_str("is");
buf.newline();
let mut it = branches.iter().enumerate().peekable();
while let Some((branch_index, branch)) = it.next() {
for (branch_index, branch) in branches.iter().enumerate() {
let expr = &branch.value;
let patterns = &branch.patterns;
let is_multiline_expr = expr.is_multiline();
let is_multiline_patterns = is_when_patterns_multiline(branch);
for (index, pattern) in patterns.iter().enumerate() {
if index == 0 {
for (pattern_index, pattern) in patterns.iter().enumerate() {
if pattern_index == 0 {
match &pattern.value {
Pattern::SpaceBefore(sub_pattern, spaces) if branch_index == 0 => {
// Never include extra newlines before the first branch.
// Instead, write the comments and that's it.
Pattern::SpaceBefore(sub_pattern, spaces) => {
if branch_index > 0 // Never render newlines before the first branch.
&& matches!(spaces.first(), Some(CommentOrNewline::Newline))
{
buf.ensure_ends_in_newline();
}
// Write comments (which may have been attached to the previous
// branch's expr, if there was a previous branch).
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + INDENT);
if branch_index > 0 {
buf.ensure_ends_in_newline();
}
fmt_pattern(buf, sub_pattern, indent + INDENT, Parens::NotNeeded);
}
other => {
buf.ensure_ends_in_newline();
fmt_pattern(buf, other, indent + INDENT, Parens::NotNeeded);
}
}
@ -747,15 +757,16 @@ fn fmt_when<'a, 'buf>(
buf.push_str(" ->");
if is_multiline_expr {
buf.newline();
} else {
buf.spaces(1);
}
match expr.value {
Expr::SpaceBefore(nested, spaces) => {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + (INDENT * 2));
fmt_spaces_no_blank_lines(buf, spaces.iter(), indent + (INDENT * 2));
if is_multiline_expr {
buf.ensure_ends_in_newline();
} else {
buf.spaces(1);
}
nested.format_with_options(
buf,
Parens::NotNeeded,
@ -764,6 +775,12 @@ fn fmt_when<'a, 'buf>(
);
}
_ => {
if is_multiline_expr {
buf.ensure_ends_in_newline();
} else {
buf.spaces(1);
}
expr.format_with_options(
buf,
Parens::NotNeeded,
@ -772,10 +789,6 @@ fn fmt_when<'a, 'buf>(
);
}
}
if it.peek().is_some() {
buf.newline();
}
}
}
@ -830,17 +843,34 @@ fn fmt_if<'a, 'buf>(
if is_multiline_condition {
match &loc_condition.value {
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
fmt_comments_only(buf, spaces_above_expr.iter(), NewlineAt::Top, return_indent);
Expr::SpaceBefore(expr_below, spaces_before_expr) => {
fmt_comments_only(
buf,
spaces_before_expr.iter(),
NewlineAt::Top,
return_indent,
);
buf.newline();
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
Expr::SpaceAfter(expr_above, spaces_after_expr) => {
expr_above.format(buf, return_indent);
// If any of the spaces is a newline, add a newline at the top.
// Otherwise leave it as just a comment.
let newline_at = if spaces_after_expr
.iter()
.any(|spaces| matches!(spaces, CommentOrNewline::Newline))
{
NewlineAt::Top
} else {
NewlineAt::None
};
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
spaces_after_expr.iter(),
newline_at,
return_indent,
);
buf.newline();
@ -885,12 +915,18 @@ fn fmt_if<'a, 'buf>(
Expr::SpaceAfter(expr_above, spaces_above) => {
expr_above.format(buf, return_indent);
fmt_comments_only(
buf,
spaces_above.iter(),
NewlineAt::Top,
return_indent,
);
// If any of the spaces is a newline, add a newline at the top.
// Otherwise leave it as just a comment.
let newline_at = if spaces_above
.iter()
.any(|spaces| matches!(spaces, CommentOrNewline::Newline))
{
NewlineAt::Top
} else {
NewlineAt::None
};
fmt_comments_only(buf, spaces_above.iter(), newline_at, return_indent);
buf.newline();
}
@ -1294,7 +1330,7 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
Expr::BinOps(left_side, _) => {
left_side
.iter()
.any(|(_, loc_bin_op)| match loc_bin_op.value {
.any(|(_, loc_binop)| match loc_binop.value {
BinOp::Caret
| BinOp::Star
| BinOp::Slash

View file

@ -53,7 +53,14 @@ impl<'a> Buf<'a> {
pub fn push(&mut self, ch: char) {
debug_assert!(!self.beginning_of_line);
debug_assert!(ch != '\n' && ch != ' ');
debug_assert!(
ch != '\n',
"Don't call buf.push('\\n') - rather, call buf.newline()"
);
debug_assert!(
ch != ' ',
"Don't call buf.push(' ') - rather, call buf.spaces(1)"
);
self.flush_spaces();
@ -92,7 +99,10 @@ impl<'a> Buf<'a> {
/// Ensures the current buffer ends in a newline, if it didn't already.
/// Doesn't add a newline if the buffer already ends in one.
pub fn ensure_ends_in_newline(&mut self) {
if !self.text.ends_with('\n') {
if self.spaces_to_flush > 0 {
self.flush_spaces();
self.newline();
} else if !self.text.ends_with('\n') {
self.newline()
}
}
@ -110,6 +120,18 @@ impl<'a> Buf<'a> {
pub fn fmt_end_of_file(&mut self) {
fmt_text_eof(&mut self.text)
}
pub fn ends_with_space(&self) -> bool {
self.spaces_to_flush > 0 || self.text.ends_with(' ')
}
pub fn ends_with_newline(&self) -> bool {
self.spaces_to_flush == 0 && self.text.ends_with('\n')
}
fn is_empty(&self) -> bool {
self.spaces_to_flush == 0 && self.text.is_empty()
}
}
/// Ensures the text ends in a newline with no whitespace preceding it.

View file

@ -242,13 +242,7 @@ fn fmt_imports<'a, 'buf>(
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
indent: u16,
) {
fmt_collection(
buf,
indent + INDENT,
Braces::Square,
loc_entries,
Newlines::No,
)
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
}
fn fmt_provides<'a, 'buf>(
@ -260,13 +254,7 @@ fn fmt_provides<'a, 'buf>(
fmt_collection(buf, indent, Braces::Square, loc_exposed_names, Newlines::No);
if let Some(loc_provided) = loc_provided_types {
fmt_default_spaces(buf, &[], indent);
fmt_collection(
buf,
indent + INDENT,
Braces::Curly,
loc_provided,
Newlines::No,
);
fmt_collection(buf, indent, Braces::Curly, loc_provided, Newlines::No);
}
}
@ -279,18 +267,12 @@ fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) {
}
}
fn fmt_exposes<'buf, N: Formattable + Copy>(
fn fmt_exposes<'buf, N: Formattable + Copy + core::fmt::Debug>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
indent: u16,
) {
fmt_collection(
buf,
indent + INDENT,
Braces::Square,
loc_entries,
Newlines::No,
)
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
}
pub trait FormatName {

View file

@ -3,7 +3,7 @@ use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::{
ast::{
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Derived, Expr, Has,
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Defs, Derived, Expr, Has,
HasClause, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
TypeHeader, ValueDef, WhenBranch,
},
@ -32,26 +32,44 @@ pub fn fmt_default_spaces<'a, 'buf>(
}
}
/// Like fmt_spaces, but disallows two consecutive newlines.
pub fn fmt_spaces_no_blank_lines<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
fmt_spaces_max_consecutive_newlines(buf, spaces, 1, indent)
}
pub fn fmt_spaces<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
fmt_spaces_max_consecutive_newlines(buf, spaces, 2, indent)
}
fn fmt_spaces_max_consecutive_newlines<'a, 'buf, I>(
buf: &mut Buf<'buf>,
spaces: I,
max_consecutive_newlines: usize,
indent: u16,
) where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
// Only ever print two newlines back to back.
// (Two newlines renders as one blank line.)
let mut consecutive_newlines = 0;
let mut encountered_comment = false;
for space in spaces {
match space {
Newline => {
if !encountered_comment && (consecutive_newlines < 2) {
if !encountered_comment && (consecutive_newlines < max_consecutive_newlines) {
buf.newline();
// Don't bother incrementing it if we're already over the limit.
// There's no upside, and it might eventually overflow,
// There's no upside, and it might eventually overflow.
consecutive_newlines += 1;
}
}
@ -125,6 +143,12 @@ pub fn fmt_comments_only<'a, 'buf, I>(
}
fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) {
// The '#' in a comment should always be preceded by a newline or a space,
// unless it's the very beginning of the buffer.
if !buf.is_empty() && !buf.ends_with_space() && !buf.ends_with_newline() {
buf.spaces(1);
}
buf.push('#');
if !comment.starts_with(' ') {
buf.spaces(1);
@ -158,6 +182,12 @@ where
}
fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
// The "##" in a doc comment should always be preceded by a newline or a space,
// unless it's the very beginning of the buffer.
if !buf.is_empty() && !buf.ends_with_space() && !buf.ends_with_newline() {
buf.spaces(1);
}
buf.push_str("##");
if !docs.is_empty() {
buf.spaces(1);
@ -183,27 +213,35 @@ impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
module: self.module.remove_spaces(arena),
defs: {
let mut defs = self.defs.clone();
for type_def in defs.type_defs.iter_mut() {
*type_def = type_def.remove_spaces(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.remove_spaces(arena);
}
for region_def in defs.regions.iter_mut() {
*region_def = region_def.remove_spaces(arena);
}
defs
},
defs: self.defs.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Defs<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut defs = self.clone();
defs.spaces.clear();
defs.space_before.clear();
defs.space_after.clear();
for type_def in defs.type_defs.iter_mut() {
*type_def = type_def.remove_spaces(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.remove_spaces(arena);
}
for region_def in defs.regions.iter_mut() {
*region_def = region_def.remove_spaces(arena);
}
defs
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {

View file

@ -199,6 +199,31 @@ mod test_fmt {
));
}
#[test]
fn comment_with_trailing_space() {
expr_formats_to(
&format!(
indoc!(
r#"
# first comment{space}
x = 0 # second comment{space}
x
"#
),
space = " ",
),
indoc!(
r#"
# first comment
x = 0 # second comment
x
"#
),
);
}
#[test]
fn def_with_inline_comment() {
expr_formats_same(indoc!(
@ -3516,34 +3541,6 @@ mod test_fmt {
);
}
#[test]
fn when_with_moving_comments() {
expr_formats_to(
indoc!(
r#"
when b is
1 ->
1 # when 1
# fall through
_ ->
2
"#
),
indoc!(
r#"
when b is
1 ->
1
# when 1
# fall through
_ ->
2
"#
),
);
}
#[test]
fn multi_line_when_condition_1() {
expr_formats_same(indoc!(
@ -3958,6 +3955,108 @@ mod test_fmt {
);
}
#[test]
fn multiline_binop_with_comments() {
expr_formats_same(indoc!(
r#"
x = 1
+ 1 # comment 1
- 1 # comment 2
* 1 # comment 3
x
"#
));
expr_formats_same(indoc!(
r#"
x = 1
+ 1 # comment 1
* 1 # comment 2
x
"#
));
expr_formats_same(indoc!(
r#"
x = 1
+ 1 # comment
x
"#
));
expr_formats_same(indoc!(
r#"
x = 1
* 1
+ 1 # comment
x
"#
));
expr_formats_same(indoc!(
r#"
x = 1
- 1
* 1
+ 1
x
"#
));
}
#[test]
fn multiline_binop_if_with_comments() {
expr_formats_same(indoc!(
r#"
if
x
+ 1 # comment 1
> 0 # comment 2
then
y
* 2 # comment 3
< 1 # comment 4
else
42
"#
));
}
#[test]
fn multiline_binop_when_with_comments() {
expr_formats_same(indoc!(
r#"
when
x
+ 1 # comment 1
> 0 # comment 2
is
y ->
3
* 2 # comment 3
< 1 # comment 4
z ->
4
/ 5 # comment 5
< 1 # comment 6
46 # first pattern comment
| 95 # alternative comment 1
| 126 # alternative comment 2
| 150 -> # This comment goes after the ->
# This comment is for the expr
Str.appendScalar output (Num.toU32 byte)
|> Result.withDefault "" # this will never fail
_ ->
42
"#
));
}
#[test]
fn precedence_conflict_greater_than() {
expr_formats_same(indoc!(
@ -4266,7 +4365,7 @@ mod test_fmt {
indoc!(
r#"
interface Foo exposes [] imports []
a = 42# Yay greetings
a = 42 # Yay greetings
"#
),
);
@ -4307,17 +4406,15 @@ mod test_fmt {
module_formats_same(indoc!(
r#"
interface Foo
exposes
[
Stuff,
Things,
somethingElse,
]
imports
[
Blah,
Baz.{ stuff, things },
]"#
exposes [
Stuff,
Things,
somethingElse,
]
imports [
Blah,
Baz.{ stuff, things },
]"#
));
}
@ -4341,6 +4438,37 @@ mod test_fmt {
);
}
#[test]
fn module_defs_with_comments() {
module_formats_to(
&format!(
indoc!(
r#"
interface Foo
exposes []
imports []
# comment 1{space}
def = "" # comment 2{space}
# comment 3{space}
"#
),
space = " "
),
indoc!(
r#"
interface Foo
exposes []
imports []
# comment 1
def = "" # comment 2
# comment 3
"#
),
);
}
#[test]
fn format_tui_package_config() {
// At one point this failed to reformat.
@ -4387,23 +4515,20 @@ mod test_fmt {
module_formats_same(indoc!(
r#"
hosted Foo
exposes
[
Stuff,
Things,
somethingElse,
]
imports
[
Blah,
Baz.{ stuff, things },
]
generates Bar with
[
map,
after,
loop,
]"#
exposes [
Stuff,
Things,
somethingElse,
]
imports [
Blah,
Baz.{ stuff, things },
]
generates Bar with [
map,
after,
loop,
]"#
));
}
@ -4522,11 +4647,11 @@ mod test_fmt {
expr_formats_same(indoc!(
r#"
Expr : [
Add Expr Expr,
Mul Expr Expr,
Val I64,
Var I64,
]
Add Expr Expr,
Mul Expr Expr,
Val I64,
Var I64,
]
Expr"#
));

View file

@ -3525,15 +3525,31 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
let it = args.iter().zip(roc_function.get_type().get_param_types());
for (arg, fastcc_type) in it {
let it = args
.iter()
.zip(roc_function.get_type().get_param_types())
.zip(arguments);
for ((arg, fastcc_type), layout) in it {
let arg_type = arg.get_type();
if arg_type == fastcc_type {
// the C and Fast calling conventions agree
arguments_for_call.push(*arg);
} else {
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type");
arguments_for_call.push(cast);
match layout {
Layout::Builtin(Builtin::List(_)) => {
let loaded = env
.builder
.build_load(arg.into_pointer_value(), "load_list_pointer");
let cast =
complex_bitcast_check_size(env, loaded, fastcc_type, "to_fastcc_type_1");
arguments_for_call.push(cast);
}
_ => {
let cast =
complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type_1");
arguments_for_call.push(cast);
}
}
}
}
@ -3657,7 +3673,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
// the C and Fast calling conventions agree
*arg
} else {
complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type")
complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type_2")
}
});
@ -4165,6 +4181,89 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
promote_to_main_function(env, mod_solutions, entry_point.symbol, entry_point.layout)
}
pub fn build_procedures_expose_expects<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
opt_level: OptLevel,
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
) -> Vec<'a, &'a str> {
use bumpalo::collections::CollectIn;
// this is not entirely accurate: it will treat every top-level bool value (turned into a
// zero-argument thunk) as an expect.
let expects: Vec<_> = procedures
.keys()
.filter_map(|(symbol, proc_layout)| {
if proc_layout.arguments.is_empty() && proc_layout.result == Layout::bool() {
Some(*symbol)
} else {
None
}
})
.collect_in(env.arena);
let mod_solutions = build_procedures_help(
env,
opt_level,
procedures,
entry_point,
Some(Path::new("/tmp/test.ll")),
);
let captures_niche = CapturesNiche::no_niche();
let top_level = ProcLayout {
arguments: &[],
result: Layout::bool(),
captures_niche,
};
let mut expect_names = Vec::with_capacity_in(expects.len(), env.arena);
for symbol in expects.iter().copied() {
let it = top_level.arguments.iter().copied();
let bytes =
roc_alias_analysis::func_name_bytes_help(symbol, it, captures_niche, &top_level.result);
let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs();
let func_spec = it.next().unwrap();
debug_assert!(
it.next().is_none(),
"we expect only one specialization of this symbol"
);
// NOTE fake layout; it is only used for debug prints
let roc_main_fn = function_value_by_func_spec(
env,
*func_spec,
symbol,
&[],
captures_niche,
&Layout::UNIT,
);
let name = roc_main_fn.get_name().to_str().unwrap();
let expect_name = &format!("Expect_{}", name);
let expect_name = env.arena.alloc_str(expect_name);
expect_names.push(&*expect_name);
// Add main to the module.
let _ = expose_function_to_host_help_c_abi(
env,
name,
roc_main_fn,
top_level.arguments,
top_level.result,
&format!("Expect_{}", name),
);
}
expect_names
}
fn build_procedures_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
opt_level: OptLevel,
@ -6007,6 +6106,20 @@ fn run_low_level<'a, 'ctx, 'env>(
PtrCast | RefCountInc | RefCountDec => {
unreachable!("Not used in LLVM backend: {:?}", op);
}
Unreachable => match RocReturn::from_layout(env, layout) {
RocReturn::Return => {
let basic_type = basic_type_from_layout(env, layout);
basic_type.const_zero()
}
RocReturn::ByPointer => {
let basic_type = basic_type_from_layout(env, layout);
let ptr = env.builder.build_alloca(basic_type, "unreachable_alloca");
env.builder.build_store(ptr, basic_type.const_zero());
ptr.into()
}
},
}
}

View file

@ -12,6 +12,26 @@ pub struct RocCallResult<T> {
value: MaybeUninit<T>,
}
impl<T> RocCallResult<T> {
pub fn new(value: T) -> Self {
Self {
tag: 0,
error_msg: std::ptr::null_mut(),
value: MaybeUninit::new(value),
}
}
}
impl<T: Default> Default for RocCallResult<T> {
fn default() -> Self {
Self {
tag: 0,
error_msg: std::ptr::null_mut(),
value: MaybeUninit::new(Default::default()),
}
}
}
impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
fn from(call_result: RocCallResult<T>) -> Self {
match call_result.tag {
@ -30,6 +50,29 @@ impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
}
}
#[macro_export]
macro_rules! run_roc_dylib {
($lib:expr, $main_fn_name:expr, $argument_type:ty, $return_type:ty, $errors:expr) => {{
use inkwell::context::Context;
use roc_builtins::bitcode;
use roc_gen_llvm::run_roc::RocCallResult;
use std::mem::MaybeUninit;
// NOTE: return value is not first argument currently (may/should change in the future)
type Main = unsafe extern "C" fn($argument_type, *mut RocCallResult<$return_type>);
unsafe {
let main: libloading::Symbol<Main> = $lib
.get($main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
main
}
}};
}
#[macro_export]
macro_rules! run_jit_function {
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{

View file

@ -1696,6 +1696,17 @@ impl<'a> LowLevelCall<'a> {
BoxExpr | UnboxExpr => {
unreachable!("The {:?} operation is turned into mono Expr", self.lowlevel)
}
Unreachable => match self.ret_storage {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
ValueType::I32 => backend.code_builder.i32_const(0),
ValueType::I64 => backend.code_builder.i64_const(0),
ValueType::F32 => backend.code_builder.f32_const(0.0),
ValueType::F64 => backend.code_builder.f64_const(0.0),
},
StoredValue::StackMemory { .. } => { /* do nothing */ }
},
}
}

View file

@ -121,6 +121,7 @@ pub enum LowLevel {
RefCountDec,
BoxExpr,
UnboxExpr,
Unreachable,
}
macro_rules! higher_order {

View file

@ -1271,6 +1271,7 @@ define_builtins! {
61 LIST_REPLACE_UNSAFE: "replaceUnsafe"
62 LIST_WITH_CAPACITY: "withCapacity"
63 LIST_ITERATE: "iterate"
64 LIST_UNREACHABLE: "unreachable"
}
6 RESULT: "Result" => {
0 RESULT_RESULT: "Result" // the Result.Result type alias

View file

@ -884,6 +884,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// - arguments that we may want to update destructively must be Owned
// - other refcounted arguments are Borrowed
match op {
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes => {
arena.alloc_slice_copy(&[borrowed])
}

View file

@ -15,6 +15,7 @@ use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::{
ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION,
ROC_PRINT_RUNTIME_ERROR_GEN,
};
use roc_derive_key::GlobalDerivedSymbols;
use roc_error_macros::{internal_error, todo_abilities};
@ -2807,22 +2808,26 @@ fn generate_runtime_error_function<'a>(
)
.unwrap();
eprintln!(
"emitted runtime error function {:?} for layout {:?}",
&msg, layout
);
dbg_do!(ROC_PRINT_RUNTIME_ERROR_GEN, {
eprintln!(
"emitted runtime error function {:?} for layout {:?}",
&msg, layout
);
});
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
let (args, ret_layout) = match layout {
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
let real_arg_layouts = lambda_set.extend_argument_list(env.arena, arg_layouts);
let mut args = Vec::with_capacity_in(real_arg_layouts.len(), env.arena);
for arg in arg_layouts {
args.push((*arg, env.unique_symbol()));
}
args.push((Layout::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
if real_arg_layouts.len() != arg_layouts.len() {
args.push((Layout::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
}
(args.into_bump_slice(), *ret_layout)
}
@ -2878,6 +2883,15 @@ fn specialize_external<'a>(
let specialized =
build_specialized_proc_from_var(env, layout_cache, lambda_name, pattern_symbols, fn_var)?;
let recursivity = if partial_proc.is_self_recursive {
SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol()))
} else {
SelfRecursive::NotSelfRecursive
};
let body = partial_proc.body.clone();
let body_var = partial_proc.body_var;
// determine the layout of aliases/rigids exposed to the host
let host_exposed_layouts = if host_exposed_variables.is_empty() {
HostExposedLayouts::NotHostExposed
@ -2922,6 +2936,7 @@ fn specialize_external<'a>(
let body = match_on_lambda_set(
env,
procs,
lambda_set,
Symbol::ARG_CLOSURE,
argument_symbols.into_bump_slice(),
@ -2965,15 +2980,7 @@ fn specialize_external<'a>(
}
};
let recursivity = if partial_proc.is_self_recursive {
SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol()))
} else {
SelfRecursive::NotSelfRecursive
};
let body = partial_proc.body.clone();
let mut specialized_body = from_can(env, partial_proc.body_var, body, procs, layout_cache);
let mut specialized_body = from_can(env, body_var, body, procs, layout_cache);
match specialized {
SpecializedLayout::FunctionPointerBody {
@ -4712,6 +4719,7 @@ pub fn with_hole<'a>(
result = match_on_lambda_set(
env,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
@ -4752,6 +4760,7 @@ pub fn with_hole<'a>(
result = match_on_lambda_set(
env,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
@ -4809,6 +4818,7 @@ pub fn with_hole<'a>(
result = match_on_lambda_set(
env,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
@ -7485,6 +7495,7 @@ fn call_by_name<'a>(
let result = match_on_lambda_set(
env,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
@ -8030,6 +8041,7 @@ fn call_specialized_proc<'a>(
let new_hole = match_on_lambda_set(
env,
procs,
lambda_set,
closure_data_symbol,
field_symbols,
@ -9103,6 +9115,7 @@ where
#[allow(clippy::too_many_arguments)]
fn match_on_lambda_set<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
lambda_set: LambdaSet<'a>,
closure_data_symbol: Symbol,
argument_symbols: &'a [Symbol],
@ -9146,7 +9159,22 @@ fn match_on_lambda_set<'a>(
field_layouts,
field_order_hash,
} => {
let function_symbol = lambda_set.iter_set().next().unwrap();
let function_symbol = match lambda_set.iter_set().next() {
Some(function_symbol) => function_symbol,
None => {
// Lambda set is empty, so this function is never called; synthesize a function
// that always yields a runtime error.
let name = env.unique_symbol();
let function_layout =
RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout);
let proc = generate_runtime_error_function(env, name, function_layout);
let top_level =
ProcLayout::from_raw(env.arena, function_layout, CapturesNiche::no_niche());
procs.specialized.insert_specialized(name, top_level, proc);
LambdaName::no_niche(name)
}
};
let closure_info = match field_layouts {
[] => ClosureInfo::DoesNotCapture,

View file

@ -16,7 +16,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
encode_unicode = "0.3.6"
[dev-dependencies]
criterion = { version = "0.3.5", features = ["html_reports"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]}
pretty_assertions = "1.0.0"
indoc = "1.0.3"
quickcheck = "1.0.3"

View file

@ -43,6 +43,7 @@ libloading = "0.7.1"
wasmer-wasi = "2.2.1"
tempfile = "3.2.0"
indoc = "1.0.3"
criterion = { git = "https://github.com/Anton-4/criterion.rs" }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
@ -57,3 +58,7 @@ gen-llvm = []
gen-dev = []
gen-wasm = []
wasm-cli-run = []
[[bench]]
name = "list_map"
harness = false

View file

@ -0,0 +1,119 @@
#[path = "../src/helpers/mod.rs"]
mod helpers;
// defines roc_alloc and friends
pub use helpers::platform_functions::*;
use bumpalo::Bump;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use roc_gen_llvm::{run_roc::RocCallResult, run_roc_dylib};
use roc_mono::ir::OptLevel;
use roc_std::RocList;
// results July 6th, 2022
//
// roc sum map time: [612.73 ns 614.24 ns 615.98 ns]
// roc sum map_with_index time: [5.3177 us 5.3218 us 5.3255 us]
// rust (debug) time: [24.081 us 24.163 us 24.268 us]
type Input = RocList<i64>;
type Output = i64;
type Main<I, O> = unsafe extern "C" fn(I, *mut RocCallResult<O>);
const ROC_LIST_MAP: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> Nat
main = \list ->
list
|> List.map (\x -> x + 2)
|> List.len
"#
);
const ROC_LIST_MAP_WITH_INDEX: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> Nat
main = \list ->
list
|> List.mapWithIndex (\x, _ -> x + 2)
|> List.len
"#
);
fn roc_function<'a, 'b>(
arena: &'a Bump,
source: &str,
) -> libloading::Symbol<'a, Main<&'b Input, Output>> {
let config = helpers::llvm::HelperConfig {
is_gen_test: true,
ignore_problems: false,
add_debug_info: true,
opt_level: OptLevel::Optimize,
};
let context = inkwell::context::Context::create();
let (main_fn_name, errors, lib) =
helpers::llvm::helper(arena, config, source, arena.alloc(context));
assert!(errors.is_empty(), "Encountered errors:\n{}", errors);
run_roc_dylib!(arena.alloc(lib), main_fn_name, &Input, Output, errors)
}
fn rust_main(argument: &RocList<i64>, output: &mut RocCallResult<i64>) {
let mut answer = 0;
for x in argument.iter() {
answer += x;
}
*output = RocCallResult::new(answer);
}
fn create_input_list() -> RocList<i64> {
let numbers = Vec::from_iter(0..1_000);
RocList::from_slice(&numbers)
}
pub fn criterion_benchmark(c: &mut Criterion) {
let arena = Bump::new();
let list_map_with_index_main = roc_function(&arena, ROC_LIST_MAP_WITH_INDEX);
let list_map_main = roc_function(&arena, ROC_LIST_MAP);
let input = &*arena.alloc(create_input_list());
c.bench_function("roc sum map", |b| {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
list_map_main(black_box(input), &mut main_result);
})
});
c.bench_function("roc sum map_with_index", |b| {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
list_map_with_index_main(black_box(input), &mut main_result);
})
});
let input = &*arena.alloc(create_input_list());
c.bench_function("rust", |b| {
b.iter(|| {
let mut main_result = RocCallResult::default();
rust_main(black_box(input), &mut main_result);
})
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View file

@ -2958,3 +2958,37 @@ fn with_capacity() {
RocList<u64>
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn call_function_in_empty_list() {
assert_evals_to!(
indoc!(
r#"
lst : List ({} -> {})
lst = []
List.map lst \f -> f {}
"#
),
RocList::from_slice(&[]),
RocList<()>
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
// TODO to be improved, if we can generate the void function type for the function in the list,
// this should succeed.
#[should_panic(expected = r#"Roc failed with message: "#)]
fn call_function_in_empty_list_unbound() {
assert_evals_to!(
indoc!(
r#"
lst = []
List.map lst \f -> f {}
"#
),
RocList::from_slice(&[]),
RocList<()>
)
}

View file

@ -1,7 +1,7 @@
use crate::helpers::from_wasmer_memory::FromWasmerMemory;
use inkwell::module::Module;
use libloading::Library;
use roc_build::link::module_to_dylib;
use roc_build::link::llvm_module_to_dylib;
use roc_build::program::FunctionIterator;
use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
@ -246,39 +246,84 @@ fn create_llvm_module<'a>(
(main_fn_name, delayed_errors.join("\n"), env.module)
}
#[derive(Debug, Clone, Copy)]
pub struct HelperConfig {
pub is_gen_test: bool,
pub ignore_problems: bool,
pub add_debug_info: bool,
pub opt_level: OptLevel,
}
#[allow(dead_code)]
#[inline(never)]
pub fn helper<'a>(
arena: &'a bumpalo::Bump,
config: HelperConfig,
src: &str,
is_gen_test: bool,
ignore_problems: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) {
let target = target_lexicon::Triple::host();
let opt_level = if cfg!(debug_assertions) {
OptLevel::Normal
} else {
OptLevel::Optimize
};
let (main_fn_name, delayed_errors, module) = create_llvm_module(
arena,
src,
is_gen_test,
ignore_problems,
config.is_gen_test,
config.ignore_problems,
context,
&target,
opt_level,
config.opt_level,
);
let lib =
module_to_dylib(module, &target, opt_level).expect("Error loading compiled dylib for test");
let res_lib = if config.add_debug_info {
let module = annotate_with_debug_info(module, context);
llvm_module_to_dylib(&module, &target, config.opt_level)
} else {
llvm_module_to_dylib(module, &target, config.opt_level)
};
let lib = res_lib.expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors, lib)
}
fn annotate_with_debug_info<'ctx>(
module: &Module<'ctx>,
context: &'ctx inkwell::context::Context,
) -> Module<'ctx> {
use std::process::Command;
let app_ll_file = "/tmp/roc-debugir.ll";
let app_dbg_ll_file = "/tmp/roc-debugir.dbg.ll";
let app_bc_file = "/tmp/roc-debugir.bc";
// write the ll code to a file, so we can modify it
module.print_to_file(&app_ll_file).unwrap();
// run the debugir https://github.com/vaivaswatha/debugir tool
match Command::new("debugir")
.args(&["-instnamer", app_ll_file])
.output()
{
Ok(_) => {}
Err(error) => {
use std::io::ErrorKind;
match error.kind() {
ErrorKind::NotFound => panic!(
r"I could not find the `debugir` tool on the PATH, install it from https://github.com/vaivaswatha/debugir"
),
_ => panic!("{:?}", error),
}
}
}
Command::new("llvm-as")
.args(&[app_dbg_ll_file, "-o", app_bc_file])
.output()
.unwrap();
inkwell::module::Module::parse_bitcode_from_path(&app_bc_file, context).unwrap()
}
fn wasm32_target_tripple() -> Triple {
use target_lexicon::{Architecture, BinaryFormat};
@ -513,13 +558,26 @@ macro_rules! assert_llvm_evals_to {
use bumpalo::Bump;
use inkwell::context::Context;
use roc_gen_llvm::run_jit_function;
use roc_mono::ir::OptLevel;
let arena = Bump::new();
let context = Context::create();
let is_gen_test = true;
let opt_level = if cfg!(debug_assertions) {
OptLevel::Normal
} else {
OptLevel::Optimize
};
let config = $crate::helpers::llvm::HelperConfig {
is_gen_test: true,
add_debug_info: false,
ignore_problems: $ignore_problems,
opt_level,
};
let (main_fn_name, errors, lib) =
$crate::helpers::llvm::helper(&arena, $src, is_gen_test, $ignore_problems, &context);
$crate::helpers::llvm::helper(&arena, config, $src, &context);
let transform = |success| {
let expected = $expected;

View file

@ -1,5 +1,7 @@
extern crate bumpalo;
pub mod platform_functions;
#[cfg(feature = "gen-dev")]
pub mod dev;
pub mod from_wasmer_memory;

View file

@ -0,0 +1,54 @@
use core::ffi::c_void;
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
libc::memcpy(dest, src, bytes)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use roc_gen_llvm::llvm::build::PanicTagId;
use std::ffi::CStr;
use std::os::raw::c_char;
match PanicTagId::try_from(tag_id) {
Ok(PanicTagId::NullTerminatedString) => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
Err(_) => unreachable!(),
}
}

View file

@ -22,57 +22,4 @@ pub mod wasm_str;
#[cfg(feature = "gen-wasm")]
pub mod wasm_linking;
use core::ffi::c_void;
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
libc::memcpy(dest, src, bytes)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use roc_gen_llvm::llvm::build::PanicTagId;
use std::ffi::CStr;
use std::os::raw::c_char;
match PanicTagId::try_from(tag_id) {
Ok(PanicTagId::NullTerminatedString) => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
Err(_) => unreachable!(),
}
}
pub use helpers::platform_functions::*;

View file

@ -0,0 +1,18 @@
procedure List.5 (#Attr.2, #Attr.3):
let List.279 : List {} = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3;
decref #Attr.2;
ret List.279;
procedure Test.2 (Test.3):
let Test.7 : {} = Struct {};
let Test.6 : {} = CallByName Test.8 Test.7;
ret Test.6;
procedure Test.8 (Test.9):
Error The `#UserApp.IdentId(8)` function could not be generated, likely due to a type error.
procedure Test.0 ():
let Test.1 : List {} = Array [];
let Test.5 : {} = Struct {};
let Test.4 : List {} = CallByName List.5 Test.1 Test.5;
ret Test.4;

View file

@ -0,0 +1,6 @@
procedure List.5 (#Attr.2, #Attr.3):
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 5030
procedure Test.0 ():
let Test.1 : List {} = Array [];
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 4557

View file

@ -1697,3 +1697,24 @@ fn recursive_call_capturing_function() {
"#
)
}
#[mono_test]
fn call_function_in_empty_list() {
indoc!(
r#"
lst : List ({} -> {})
lst = []
List.map lst \f -> f {}
"#
)
}
#[mono_test]
fn call_function_in_empty_list_unbound() {
indoc!(
r#"
lst = []
List.map lst \f -> f {}
"#
)
}