Merge branch 'trunk' into builtins-list-intersperse

This commit is contained in:
satotake 2021-11-18 11:16:27 +00:00 committed by GitHub
commit ce8a88416d
319 changed files with 4413 additions and 3654 deletions

View file

@ -52,3 +52,5 @@ Ayaz Hafiz <ayaz.hafiz.1@gmail.com>
Johannes Maas <github@j-maas.de>
Takeshi Sato <doublequotation@gmail.com>
Joost Baas <joost@joostbaas.eu>
Callum Dunster <cdunster@users.noreply.github.com>
Martin Stewart <MartinSStewart@gmail.com>

2
Cargo.lock generated
View file

@ -3476,7 +3476,9 @@ dependencies = [
name = "roc_parse"
version = "0.1.0"
dependencies = [
"ansi_term",
"bumpalo",
"diff",
"encode_unicode",
"indoc",
"pretty_assertions",

View file

@ -12,7 +12,6 @@ members = [
"compiler/constrain",
"compiler/unify",
"compiler/solve",
"compiler/reporting",
"compiler/fmt",
"compiler/mono",
"compiler/test_mono",
@ -31,6 +30,7 @@ members = [
"ast",
"cli",
"code_markup",
"reporting",
"roc_std",
"utils",
"docs",

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
COPY --dir cli cli_utils compiler docs editor ast code_markup utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt

View file

@ -20,6 +20,7 @@ use crate::{
expr2::{ClosureExtra, Expr2, ExprId, WhenBranch},
record_field::RecordField,
},
fun_def::FunctionDef,
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
types::{Type2, TypeId},
val_def::ValueDef,
@ -818,6 +819,126 @@ pub fn constrain_expr<'a>(
}
}
}
// In an expression like
// id = \x -> x
//
// id 1
// The `def_id` refers to the definition `id = \x -> x`,
// and the body refers to `id 1`.
Expr2::LetFunction {
def_id,
body_id,
body_var: _,
} => {
let body = env.pool.get(*body_id);
let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region);
let function_def = env.pool.get(*def_id);
let (name, arguments, body_id, rigid_vars, args_constrs) = match function_def {
FunctionDef::WithAnnotation {
name,
arguments,
body_id,
rigids,
return_type: _,
} => {
// The annotation gives us arguments with proper Type2s, but the constraints we
// generate below args bound to type variables. Create fresh ones and bind them
// to the types we already know.
let mut args_constrs = BumpVec::with_capacity_in(arguments.len(), arena);
let args_vars = PoolVec::with_capacity(arguments.len() as u32, env.pool);
for (arg_ty_node_id, arg_var_node_id) in
arguments.iter_node_ids().zip(args_vars.iter_node_ids())
{
let (ty, pattern) = env.pool.get(arg_ty_node_id);
let arg_var = env.var_store.fresh();
let ty = env.pool.get(*ty);
args_constrs.push(Eq(
Type2::Variable(arg_var),
Expected::NoExpectation(ty.shallow_clone()),
Category::Storage(std::file!(), std::line!()),
// TODO: should be the actual region of the argument
region,
));
env.pool[arg_var_node_id] = (arg_var, *pattern);
}
let rigids = env.pool.get(*rigids);
let rigid_vars: BumpVec<Variable> =
BumpVec::from_iter_in(rigids.names.iter(env.pool).map(|&(_, v)| v), arena);
(name, args_vars, body_id, rigid_vars, args_constrs)
}
FunctionDef::NoAnnotation {
name,
arguments,
body_id,
return_var: _,
} => {
(
name,
arguments.shallow_clone(),
body_id,
BumpVec::new_in(arena), // The function is unannotated, so there are no rigid type vars
BumpVec::new_in(arena), // No extra constraints to generate for arguments
)
}
};
// A function definition is equivalent to a named value definition, where the
// value is a closure. So, we create a closure definition in correspondence
// with the function definition, generate type constraints for it, and demand
// that type of the function is just the type of the resolved closure.
let fn_var = env.var_store.fresh();
let fn_ty = Type2::Variable(fn_var);
let extra = ClosureExtra {
return_type: env.var_store.fresh(),
captured_symbols: PoolVec::empty(env.pool),
closure_type: env.var_store.fresh(),
closure_ext_var: env.var_store.fresh(),
};
let clos = Expr2::Closure {
args: arguments.shallow_clone(),
uniq_symbol: *name,
body_id: *body_id,
function_type: env.var_store.fresh(),
extra: env.pool.add(extra),
recursive: roc_can::expr::Recursive::Recursive,
};
let clos_con = constrain_expr(
arena,
env,
&clos,
Expected::NoExpectation(fn_ty.shallow_clone()),
region,
);
// This is the `foo` part in `foo = \...`. We want to bind the name of the
// function with its type, whose constraints we generated above.
let mut def_pattern_state = PatternState2 {
headers: BumpMap::new_in(arena),
vars: BumpVec::new_in(arena),
constraints: args_constrs,
};
def_pattern_state.headers.insert(*name, fn_ty);
def_pattern_state.vars.push(fn_var);
Let(arena.alloc(LetConstraint {
rigid_vars,
flex_vars: def_pattern_state.vars,
def_types: def_pattern_state.headers, // Binding function name -> its type
defs_constraint: Let(arena.alloc(LetConstraint {
rigid_vars: BumpVec::new_in(arena), // always empty
flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments
def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments
defs_constraint: And(def_pattern_state.constraints),
ret_constraint: clos_con,
})),
ret_constraint: body_con,
}))
}
Expr2::Update {
symbol,
updates,
@ -1031,7 +1152,6 @@ pub fn constrain_expr<'a>(
exists(arena, vars, And(and_constraints))
}
Expr2::LetRec { .. } => todo!(),
Expr2::LetFunction { .. } => todo!(),
}
}
@ -1765,7 +1885,7 @@ pub mod test_constrain {
use roc_parse::parser::SyntaxError;
use roc_region::all::Region;
use roc_types::{
pretty_print::content_to_string,
pretty_print::{content_to_string, name_all_type_vars},
solved_types::Solved,
subs::{Subs, VarStore, Variable},
};
@ -1845,7 +1965,7 @@ pub mod test_constrain {
let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region);
match expr2_result {
Ok((expr, _)) => {
Ok((expr, output)) => {
let constraint = constrain_expr(
&code_arena,
&mut env,
@ -1865,17 +1985,22 @@ pub mod test_constrain {
let mut var_store = VarStore::default();
std::mem::swap(ref_var_store, &mut var_store);
let rigids = output.introduced_variables.name_by_var;
let (mut solved, _, _) = run_solve(
&code_arena,
pool,
Default::default(),
Default::default(),
rigids,
constraint,
var_store,
);
let subs = solved.inner_mut();
// name type vars
name_all_type_vars(var, subs);
let content = subs.get_content_without_compacting(var);
// Connect the ModuleId to it's IdentIds
@ -2132,6 +2257,102 @@ pub mod test_constrain {
)
}
#[test]
fn dual_arity_lambda() {
infer_eq(
indoc!(
r#"
\a, b -> Pair a b
"#
),
"a, b -> [ Pair a b ]*",
);
}
#[test]
fn anonymous_identity() {
infer_eq(
indoc!(
r#"
(\a -> a) 3.14
"#
),
"Float *",
);
}
#[test]
fn identity_of_identity() {
infer_eq(
indoc!(
r#"
(\val -> val) (\val -> val)
"#
),
"a -> a",
);
}
#[test]
fn identity_function() {
infer_eq(
indoc!(
r#"
\val -> val
"#
),
"a -> a",
);
}
#[test]
fn apply_function() {
infer_eq(
indoc!(
r#"
\f, x -> f x
"#
),
"(a -> b), a -> b",
);
}
#[test]
fn flip_function() {
infer_eq(
indoc!(
r#"
\f -> (\a, b -> f b a)
"#
),
"(a, b -> c) -> (b, a -> c)",
);
}
#[test]
fn always_function() {
infer_eq(
indoc!(
r#"
\val -> \_ -> val
"#
),
"a -> (* -> a)",
);
}
#[test]
fn pass_a_function() {
infer_eq(
indoc!(
r#"
\f -> f {}
"#
),
"({} -> a) -> a",
);
}
#[test]
fn constrain_closure() {
infer_eq(
@ -2145,4 +2366,130 @@ pub mod test_constrain {
"{}* -> Num *",
)
}
#[test]
fn recursive_identity() {
infer_eq(
indoc!(
r#"
identity = \val -> val
identity
"#
),
"a -> a",
);
}
#[test]
fn use_apply() {
infer_eq(
indoc!(
r#"
identity = \a -> a
apply = \f, x -> f x
apply identity 5
"#
),
"Num *",
);
}
#[test]
fn nested_let_function() {
infer_eq(
indoc!(
r#"
curryPair = \a ->
getB = \b -> Pair a b
getB
curryPair
"#
),
"a -> (b -> [ Pair a b ]*)",
);
}
#[test]
fn record_with_bound_var() {
infer_eq(
indoc!(
r#"
fn = \rec ->
x = rec.x
rec
fn
"#
),
"{ x : a }b -> { x : a }b",
);
}
#[test]
fn using_type_signature() {
infer_eq(
indoc!(
r#"
bar : custom -> custom
bar = \x -> x
bar
"#
),
"custom -> custom",
);
}
#[ignore = "Currently panics at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1212:21"]
#[test]
fn using_type_signature2() {
infer_eq(
indoc!(
r#"
id1 : tya -> tya
id1 = \x -> x
id2 : tyb -> tyb
id2 = id1
id2
"#
),
"tyb -> tyb",
);
}
#[ignore = "Implement annotation-only decls"]
#[test]
fn type_signature_without_body() {
infer_eq(
indoc!(
r#"
foo: Str -> {}
foo "hi"
"#
),
"{}",
);
}
#[ignore = "Implement annotation-only decls"]
#[test]
fn type_signature_without_body_rigid() {
infer_eq(
indoc!(
r#"
foo : Num * -> custom
foo 2
"#
),
"custom",
);
}
}

View file

@ -37,7 +37,11 @@ use crate::{
rigids::Rigids,
scope::Scope,
},
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone},
mem_pool::{
pool::{NodeId, Pool},
pool_vec::PoolVec,
shallow_clone::ShallowClone,
},
};
#[derive(Debug)]
@ -316,7 +320,7 @@ fn from_pending_alias<'a>(
}
for loc_lowercase in vars {
if !named_rigids.contains_key(loc_lowercase.value.as_str()) {
if !named_rigids.contains_key(&loc_lowercase.value) {
env.problem(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
@ -454,6 +458,10 @@ fn canonicalize_pending_def<'a>(
output.references.referenced_aliases.insert(symbol);
}
// Ensure rigid type vars and their names are known in the output.
for (name, &var) in named_rigids.iter() {
output.introduced_variables.insert_named(name.clone(), var);
}
let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool);
// bookkeeping for tail-call detection. If we're assigning to an
@ -521,7 +529,7 @@ fn canonicalize_pending_def<'a>(
// parent commit for the bug this fixed!
let refs = References::new();
let arguments: PoolVec<(PatternId, Type2)> =
let arguments: PoolVec<(NodeId<Type2>, PatternId)> =
PoolVec::with_capacity(closure_args.len() as u32, env.pool);
let return_type: TypeId;
@ -558,7 +566,8 @@ fn canonicalize_pending_def<'a>(
for (node_id, ((_, pattern_id), typ)) in
arguments.iter_node_ids().zip(it.into_iter())
{
env.pool[node_id] = (pattern_id, typ);
let typ = env.pool.add(typ);
env.pool[node_id] = (typ, pattern_id);
}
return_type = return_type_id;
@ -689,14 +698,14 @@ fn canonicalize_pending_def<'a>(
// parent commit for the bug this fixed!
let refs = References::new();
let arguments: PoolVec<(PatternId, Variable)> =
let arguments: PoolVec<(Variable, PatternId)> =
PoolVec::with_capacity(closure_args.len() as u32, env.pool);
let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect();
for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter())
{
env.pool[node_id] = (pattern_id, env.var_store.fresh());
env.pool[node_id] = (env.var_store.fresh(), pattern_id);
}
let function_def = FunctionDef::NoAnnotation {

View file

@ -15,14 +15,14 @@ use super::{
pub enum FunctionDef {
WithAnnotation {
name: Symbol, // 8B
arguments: PoolVec<(PatternId, Type2)>, // 8B
arguments: PoolVec<(NodeId<Type2>, PatternId)>, // 8B
rigids: NodeId<Rigids>, // 4B
return_type: TypeId, // 4B
body_id: ExprId, // 4B
},
NoAnnotation {
name: Symbol, // 8B
arguments: PoolVec<(PatternId, Variable)>, // 8B
arguments: PoolVec<(Variable, PatternId)>, // 8B
return_var: Variable, // 4B
body_id: ExprId, // 4B
},

View file

@ -2,7 +2,7 @@ pub mod ast;
mod declaration;
pub mod def;
pub mod expr;
mod fun_def;
pub mod fun_def;
pub mod header;
pub mod pattern;
pub mod str;

View file

@ -3,7 +3,7 @@
#![allow(unused_imports)]
// use roc_can::expr::Output;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, TagName};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::types::{Problem, RecordField};
@ -20,29 +20,34 @@ pub type TypeId = NodeId<Type2>;
#[derive(Debug)]
pub enum Type2 {
Variable(Variable),
Variable(Variable), // 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
// 32B
// 24B
HostExposedAlias {
name: Symbol, // 8B
arguments: PoolVec<(PoolStr, TypeId)>, // 12B
arguments: PoolVec<(PoolStr, TypeId)>, // 8B
actual_var: Variable, // 4B
actual: TypeId, // 4B
},
EmptyTagUnion,
TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 12B + 4B
RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 20B = 4B + 12B + 4B
TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 12B = 8B + 4B
RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 4B + 8B + 4B
EmptyRec,
Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 16B = 12B + 4B
Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 12B = 8B + 4B
Function(PoolVec<Type2>, TypeId, TypeId), // 20B = 12B + 4B + 4B
Apply(Symbol, PoolVec<Type2>), // 20B = 8B + 12B
Function(PoolVec<Type2>, TypeId, TypeId), // 16B = 8B + 4B + 4B
Apply(Symbol, PoolVec<Type2>), // 16B = 8B + 8B
Erroneous(Problem2),
Erroneous(Problem2), // 24B
}
#[test]
fn type2_size() {
assert_eq!(std::mem::size_of::<Type2>(), 32); // 24B + pad
}
#[derive(Debug)]
@ -171,9 +176,9 @@ pub enum Signature {
},
}
pub enum Annotation2<'a> {
pub enum Annotation2 {
Annotation {
named_rigids: MutMap<&'a str, Variable>,
named_rigids: MutMap<Lowercase, Variable>,
unnamed_rigids: MutSet<Variable>,
symbols: MutSet<Symbol>,
signature: Signature,
@ -186,7 +191,7 @@ pub fn to_annotation2<'a>(
scope: &mut Scope,
annotation: &'a roc_parse::ast::TypeAnnotation<'a>,
region: Region,
) -> Annotation2<'a> {
) -> Annotation2 {
let mut references = References::default();
let annotation = to_type2(env, scope, &mut references, annotation, region);
@ -240,11 +245,7 @@ pub fn to_annotation2<'a>(
}
}
fn shallow_dealias<'a>(
env: &mut Env,
references: References<'a>,
annotation: Type2,
) -> Annotation2<'a> {
fn shallow_dealias<'a>(env: &mut Env, references: References, annotation: Type2) -> Annotation2 {
let References {
named,
unnamed,
@ -288,8 +289,8 @@ fn shallow_dealias<'a>(
}
#[derive(Default)]
pub struct References<'a> {
named: MutMap<&'a str, Variable>,
pub struct References {
named: MutMap<Lowercase, Variable>,
unnamed: MutSet<Variable>,
hidden: MutSet<Variable>,
symbols: MutSet<Symbol>,
@ -298,7 +299,7 @@ pub struct References<'a> {
pub fn to_type_id<'a>(
env: &mut Env,
scope: &mut Scope,
rigids: &mut References<'a>,
rigids: &mut References,
annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region,
) -> TypeId {
@ -310,7 +311,7 @@ pub fn to_type_id<'a>(
pub fn as_type_id<'a>(
env: &mut Env,
scope: &mut Scope,
rigids: &mut References<'a>,
rigids: &mut References,
type_id: TypeId,
annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region,
@ -324,7 +325,7 @@ pub fn as_type_id<'a>(
pub fn to_type2<'a>(
env: &mut Env,
scope: &mut Scope,
references: &mut References<'a>,
references: &mut References,
annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region,
) -> Type2 {
@ -375,8 +376,9 @@ pub fn to_type2<'a>(
Type2::Function(arguments, closure_type_id, return_type_id)
}
BoundVariable(v) => {
// a rigid type variable
match references.named.get(v) {
// A rigid type variable. The parser should have already ensured that the name is indeed a lowercase.
let v = Lowercase::from(*v);
match references.named.get(&v) {
Some(var) => Type2::Variable(*var),
None => {
let var = env.var_store.fresh();
@ -387,6 +389,9 @@ pub fn to_type2<'a>(
}
}
}
Inferred => {
unimplemented!();
}
Wildcard | Malformed(_) => {
let var = env.var_store.fresh();
@ -401,7 +406,7 @@ pub fn to_type2<'a>(
let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool);
for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) {
let poolstr = PoolStr::new(label, env.pool);
let poolstr = PoolStr::new(label.as_str(), env.pool);
let rec_field = match field {
RecordField::Optional(_) => {
@ -480,10 +485,10 @@ pub fn to_type2<'a>(
{
match loc_var.value {
BoundVariable(ident) => {
let var_name = ident;
let var_name = Lowercase::from(ident);
if let Some(var) = references.named.get(&var_name) {
let poolstr = PoolStr::new(var_name, env.pool);
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(*var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
@ -494,7 +499,7 @@ pub fn to_type2<'a>(
let var = env.var_store.fresh();
references.named.insert(var_name.clone(), var);
let poolstr = PoolStr::new(var_name, env.pool);
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
@ -576,10 +581,10 @@ pub fn to_type2<'a>(
fn can_assigned_fields<'a>(
env: &mut Env,
scope: &mut Scope,
rigids: &mut References<'a>,
rigids: &mut References,
fields: &&[Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>],
region: Region,
) -> MutMap<&'a str, RecordField<Type2>> {
) -> MutMap<Lowercase, RecordField<Type2>> {
use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*;
@ -602,8 +607,8 @@ fn can_assigned_fields<'a>(
let field_type =
to_type2(env, scope, rigids, &annotation.value, annotation.region);
let label = field_name.value;
field_types.insert(label, Required(field_type));
let label = Lowercase::from(field_name.value);
field_types.insert(label.clone(), Required(field_type));
break 'inner label;
}
@ -611,20 +616,20 @@ fn can_assigned_fields<'a>(
let field_type =
to_type2(env, scope, rigids, &annotation.value, annotation.region);
let label = field_name.value;
let label = Lowercase::from(field_name.value);
field_types.insert(label.clone(), Optional(field_type));
break 'inner label;
}
LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b }
let field_name = loc_field_name.value;
let field_name = Lowercase::from(loc_field_name.value);
let field_type = {
if let Some(var) = rigids.named.get(&field_name) {
Type2::Variable(*var)
} else {
let field_var = env.var_store.fresh();
rigids.named.insert(field_name, field_var);
rigids.named.insert(field_name.clone(), field_var);
Type2::Variable(field_var)
}
};
@ -664,7 +669,7 @@ fn can_assigned_fields<'a>(
fn can_tags<'a>(
env: &mut Env,
scope: &mut Scope,
rigids: &mut References<'a>,
rigids: &mut References,
tags: &'a [Located<roc_parse::ast::Tag<'a>>],
region: Region,
) -> Vec<(TagName, PoolVec<Type2>)> {
@ -748,7 +753,7 @@ enum TypeApply {
fn to_type_apply<'a>(
env: &mut Env,
scope: &mut Scope,
rigids: &mut References<'a>,
rigids: &mut References,
module_name: &str,
ident: &str,
type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>],

View file

@ -7,6 +7,7 @@ use crate::mem_pool::{
pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone,
};
use roc_collections::all::WyHash;
use roc_module::ident::Lowercase;
use roc_types::subs::Variable;
#[derive(Debug)]
@ -18,7 +19,7 @@ pub struct Rigids {
#[allow(clippy::needless_collect)]
impl Rigids {
pub fn new(
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>,
named: HashMap<Lowercase, Variable, BuildHasherDefault<WyHash>>,
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
pool: &mut Pool,
) -> Self {
@ -26,7 +27,7 @@ impl Rigids {
let mut temp_names = Vec::new();
temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var)));
temp_names.extend(named.iter().map(|(name, var)| (Some(name.as_str()), *var)));
temp_names.extend(unnamed.iter().map(|var| (None, *var)));

View file

@ -61,7 +61,7 @@ roc_load = { path = "../compiler/load" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_reporting = { path = "../reporting" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }

View file

@ -22,7 +22,7 @@ roc_load = { path = "../load" }
roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../reporting" }
roc_reporting = { path = "../../reporting" }
roc_std = { path = "../../roc_std" }
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"

View file

@ -145,6 +145,8 @@ pub fn build_zig_host_native(
_target: &str,
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;

View file

@ -870,6 +870,9 @@ pub fn listSublist(
len: usize,
dec: Dec,
) callconv(.C) RocList {
if (len == 0) {
return RocList.empty();
}
if (list.bytes) |source_ptr| {
const size = list.len();

View file

@ -1015,6 +1015,25 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))),
);
// split : List elem, Nat -> { before: List elem, others: List elem }
add_top_level_function_type!(
Symbol::LIST_SPLIT,
vec![list_type(flex(TVAR1)), nat_type(),],
Box::new(SolvedType::Record {
fields: vec![
(
"before".into(),
RecordField::Required(list_type(flex(TVAR1)))
),
(
"others".into(),
RecordField::Required(list_type(flex(TVAR1)))
),
],
ext: Box::new(SolvedType::EmptyRecord),
},),
);
// drop : List elem, Nat -> List elem
add_top_level_function_type!(
Symbol::LIST_DROP,

View file

@ -459,6 +459,9 @@ fn can_annotation_help(
Type::Variable(var)
}
Inferred => {
unimplemented!();
}
Malformed(string) => {
malformed(env, region, string);

View file

@ -1,9 +1,9 @@
use crate::def::Def;
use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Recursive, WhenBranch};
use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::pattern::Pattern;
use roc_collections::all::SendMap;
use roc_module::ident::TagName;
use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol;
@ -96,6 +96,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_TAKE_FIRST => list_take_first,
LIST_TAKE_LAST => list_take_last,
LIST_SUBLIST => list_sublist,
LIST_SPLIT => list_split,
LIST_INTERSPERSE => list_intersperse,
LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first,
@ -111,7 +113,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with,
LIST_ANY => list_any,
LIST_INTERSPERSE => list_intersperse,
LIST_FIND => list_find,
DICT_LEN => dict_len,
DICT_EMPTY => dict_empty,
@ -2226,6 +2227,117 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.split : List elem, Nat -> { before: List elem, others: List elem }
fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let list_sym = Symbol::ARG_1;
let index_sym = Symbol::ARG_2;
let clos_sym = Symbol::LIST_SPLIT_CLOS;
let clos_start_sym = Symbol::ARG_3;
let clos_len_sym = Symbol::ARG_4;
let clos_fun_var = var_store.fresh();
let clos_start_var = var_store.fresh();
let clos_len_var = var_store.fresh();
let clos_ret_var = var_store.fresh();
let ret_var = var_store.fresh();
let zero = int(index_var, Variable::NATURAL, 0);
let clos = Closure(ClosureData {
function_type: clos_fun_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: clos_ret_var,
name: clos_sym,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(list_sym, clos_ret_var)],
arguments: vec![
(
clos_start_var,
no_region(Pattern::Identifier(clos_start_sym)),
),
(clos_len_var, no_region(Pattern::Identifier(clos_len_sym))),
],
loc_body: {
Box::new(no_region(RunLowLevel {
op: LowLevel::ListSublist,
args: vec![
(clos_ret_var, Var(list_sym)),
(clos_start_var, Var(clos_start_sym)),
(clos_len_var, Var(clos_len_sym)),
],
ret_var: clos_ret_var,
}))
},
});
let fun = Box::new((
clos_fun_var,
no_region(clos),
var_store.fresh(),
clos_ret_var,
));
let get_before = Call(
fun.clone(),
vec![
(index_var, no_region(zero)),
(index_var, no_region(Var(index_sym))),
],
CalledVia::Space,
);
let get_list_len = RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(list_sym))],
ret_var: index_var,
};
let get_others_len = RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![(index_var, get_list_len), (index_var, Var(index_sym))],
ret_var: index_var,
};
let get_others = Call(
fun,
vec![
(index_var, no_region(Var(index_sym))),
(index_var, no_region(get_others_len)),
],
CalledVia::Space,
);
let before = Field {
var: clos_ret_var,
region: Region::zero(),
loc_expr: Box::new(no_region(get_before)),
};
let others = Field {
var: clos_ret_var,
region: Region::zero(),
loc_expr: Box::new(no_region(get_others)),
};
let body = record(
vec![("before".into(), before), ("others".into(), others)],
var_store,
);
defn(
symbol,
vec![(list_var, list_sym), (index_var, index_sym)],
var_store,
body,
ret_var,
)
}
/// List.drop : List elem, Nat -> List elem
fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -4388,17 +4500,17 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
}
}
// #[inline(always)]
// fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
// let mut send_map = SendMap::default();
// for (k, v) in fields {
// send_map.insert(k, v);
// }
// Expr::Record {
// record_var: var_store.fresh(),
// fields: send_map,
// }
// }
#[inline(always)]
fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
let mut send_map = SendMap::default();
for (k, v) in fields {
send_map.insert(k, v);
}
Expr::Record {
record_var: var_store.fresh(),
fields: send_map,
}
}
#[inline(always)]
fn defn(

View file

@ -180,7 +180,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
true
}
Wildcard | BoundVariable(_) | Malformed(_) => false,
Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => {
(&result.value).is_multiline()
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
@ -279,6 +279,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
}
BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'),
Inferred => buf.push('_'),
TagUnion { tags, ext } => {
format_sequence!(buf, indent, '[', ']', tags, newlines, Tag);

View file

@ -70,7 +70,7 @@ This is the general procedure I follow with some helpful links:
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.
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/trunk/compiler/gen_dev/tests/helpers/eval.rs) and run the test.
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.
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.

View file

@ -446,6 +446,10 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
unimplemented!("stack offsets over 32k are not yet implement for AArch64");
}
}
#[inline(always)]
fn neg_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) {
unimplemented!("neg is not yet implement for AArch64");
}
#[inline(always)]
fn sub_reg64_reg64_imm32(

View file

@ -149,6 +149,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
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 imul_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
@ -786,6 +787,23 @@ impl<
}
}
fn build_num_neg(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
match layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src_reg = self.load_to_general_reg(src)?;
ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg);
Ok(())
}
x => Err(format!("NumNeg: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_sub(
&mut self,
dst: &Symbol,

View file

@ -1053,6 +1053,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src)
}
#[inline(always)]
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
mov_reg64_reg64(buf, dst, src);
neg_reg64(buf, dst);
}
#[inline(always)]
fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,

View file

@ -368,6 +368,18 @@ where
);
self.build_num_mul(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumNeg => {
debug_assert_eq!(
1,
args.len(),
"NumNeg: expected to have exactly one argument"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumNeg: expected to have the same argument and return layout"
);
self.build_num_neg(sym, &args[0], ret_layout)
}
LowLevel::NumPowInt => self.build_fn_call(
sym,
bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
@ -459,6 +471,14 @@ where
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_neg stores the negated value of src into dst.
fn build_num_neg(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_sub stores the `src1 - src2` difference into dst.
fn build_num_sub(
&mut self,

Binary file not shown.

View file

@ -444,6 +444,13 @@ impl<'a> WasmBackend<'a> {
Ok(())
}
Stmt::Refcounting(_modify, following) => {
// TODO: actually deal with refcounting. For hello world, we just skipped it.
self.build_stmt(following, ret_layout)?;
Ok(())
}
x => Err(format!("statement not yet implemented: {:?}", x)),
}
}
@ -472,7 +479,7 @@ impl<'a> WasmBackend<'a> {
CallType::ByName { name: func_sym, .. } => {
// If this function is just a lowlevel wrapper, then inline it
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
return self.build_low_level(lowlevel, arguments, wasm_layout);
return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
}
let mut wasm_args_tmp: Vec<Symbol>;
@ -517,7 +524,7 @@ impl<'a> WasmBackend<'a> {
}
CallType::LowLevel { op: lowlevel, .. } => {
self.build_low_level(*lowlevel, arguments, wasm_layout)
self.build_low_level(*lowlevel, arguments, *sym, wasm_layout)
}
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
@ -556,9 +563,18 @@ impl<'a> WasmBackend<'a> {
&mut self,
lowlevel: LowLevel,
arguments: &'a [Symbol],
return_sym: Symbol,
return_layout: WasmLayout,
) -> Result<(), String> {
self.storage.load_symbols(&mut self.code_builder, arguments);
// Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use.
// It's only different from the C ABI for small structs, and we are using Zig for all of those cases.
// This is a workaround for a bug in Zig. If later versions fix it, we can change to the C ABI.
self.storage.load_symbols_fastcc(
&mut self.code_builder,
arguments,
return_sym,
&return_layout,
);
let build_result = decode_low_level(
&mut self.code_builder,
@ -572,7 +588,7 @@ impl<'a> WasmBackend<'a> {
match build_result {
Done => Ok(()),
BuiltinCall(name) => {
self.call_imported_builtin(name, arguments, &return_layout);
self.call_zig_builtin(name, arguments, &return_layout);
Ok(())
}
NotImplemented => Err(format!(
@ -626,8 +642,8 @@ impl<'a> WasmBackend<'a> {
self.lookup_string_constant(string, sym, layout);
self.code_builder.get_local(local_id);
self.code_builder.insert_memory_relocation(linker_sym_index);
self.code_builder.i32_const(elements_addr as i32);
self.code_builder
.i32_const_mem_addr(elements_addr, linker_sym_index);
self.code_builder.i32_store(Align::Bytes4, offset);
self.code_builder.get_local(local_id);
@ -749,12 +765,10 @@ impl<'a> WasmBackend<'a> {
Ok(())
}
fn call_imported_builtin(
&mut self,
name: &'a str,
arguments: &[Symbol],
ret_layout: &WasmLayout,
) {
/// Generate a call instruction to a Zig builtin function.
/// And if we haven't seen it before, add an Import and linker data for it.
/// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) {
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
@ -764,11 +778,29 @@ impl<'a> WasmBackend<'a> {
},
None => {
let mut param_types = Vec::with_capacity_in(arguments.len(), self.env.arena);
param_types.extend(arguments.iter().map(|a| self.storage.get(a).value_type()));
let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
let ret_type = if ret_layout.is_stack_memory() {
param_types.push(ValueType::I32);
None
} else {
Some(ret_layout.value_type())
};
// Zig's "fast calling convention" packs structs into CPU registers (stack machine slots) if possible.
// If they're small enough they can go into an I32 or I64. If they're big, they're pointers (I32).
for arg in arguments {
param_types.push(match self.storage.get(arg) {
StoredValue::StackMemory { size, .. } if *size > 4 && *size <= 8 => {
ValueType::I64
}
stored => stored.value_type(),
});
}
let signature_index = self.module.types.insert(Signature {
param_types,
ret_type: Some(ret_layout.value_type()), // TODO: handle builtins with no return value
ret_type,
});
let import_index = self.module.import.entries.len() as u32;

View file

@ -7,6 +7,7 @@ pub mod wasm_module;
use bumpalo::{self, collections::Vec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds;
@ -36,7 +37,7 @@ pub fn build_module<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<std::vec::Vec<u8>, String> {
let mut wasm_module = build_module_help(env, procedures)?;
let (mut wasm_module, _) = build_module_help(env, procedures)?;
let mut buffer = std::vec::Vec::with_capacity(4096);
wasm_module.serialize_mut(&mut buffer);
Ok(buffer)
@ -45,36 +46,54 @@ pub fn build_module<'a>(
pub fn build_module_help<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<WasmModule<'a>, String> {
) -> Result<(WasmModule<'a>, u32), String> {
let mut layout_ids = LayoutIds::default();
let mut proc_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut exports = Vec::with_capacity_in(procedures.len(), env.arena);
let mut exports = Vec::with_capacity_in(4, env.arena);
let mut main_fn_index = None;
// Collect the symbols & names for the procedures
for (i, (sym, layout)) in procedures.keys().enumerate() {
proc_symbols.push(*sym);
// Collect the symbols & names for the procedures,
// and filter out procs we're going to inline
let mut fn_index: u32 = 0;
for ((sym, layout), proc) in procedures.into_iter() {
if LowLevel::from_inlined_wrapper(sym).is_some() {
continue;
}
generated_procs.push(proc);
generated_symbols.push(sym);
let fn_name = layout_ids
.get_toplevel(*sym, layout)
.to_symbol_string(*sym, &env.interns);
.get_toplevel(sym, &layout)
.to_symbol_string(sym, &env.interns);
if env.exposed_to_host.contains(sym) {
if env.exposed_to_host.contains(&sym) {
main_fn_index = Some(fn_index);
exports.push(Export {
name: fn_name.clone(),
ty: ExportType::Func,
index: i as u32,
index: fn_index,
});
}
let linker_sym = SymInfo::for_function(i as u32, fn_name);
let linker_sym = SymInfo::for_function(fn_index, fn_name);
linker_symbols.push(linker_sym);
fn_index += 1;
}
// Main loop: Build the Wasm module
// Build the Wasm module
let (mut module, linker_symbols) = {
let mut backend = WasmBackend::new(env, layout_ids, proc_symbols, linker_symbols, exports);
for ((sym, _), proc) in procedures.into_iter() {
let mut backend = WasmBackend::new(
env,
layout_ids,
generated_symbols.clone(),
linker_symbols,
exports,
);
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
backend.build_proc(proc, sym)?;
}
(backend.module, backend.linker_symbols)
@ -83,7 +102,7 @@ pub fn build_module_help<'a>(
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
module.linking.subsections.push(symbol_table);
Ok(module)
Ok((module, main_fn_index.unwrap()))
}
pub struct CopyMemoryConfig {

View file

@ -27,16 +27,18 @@ pub fn decode_low_level<'a>(
let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
match lowlevel {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft
| StrTrimRight | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim
| ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap
| ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| ListSublist | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | DictEmpty
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
| DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit
| StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft | StrTrimRight
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
| ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | DictEmpty | DictInsert
| DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | DictUnion
| DictIntersection | DictDifference | DictWalk | SetFromList => {
return NotImplemented;
}

View file

@ -5,7 +5,7 @@ use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use crate::layout::WasmLayout;
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VmSymbolState};
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
pub enum StoredValueKind {
@ -194,17 +194,10 @@ impl<'a> Storage<'a> {
})
}
/// Load symbols to the top of the VM stack
/// Avoid calling this method in a loop with one symbol at a time! It will work,
/// but it generates very inefficient Wasm code.
pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) {
if code_builder.verify_stack_match(symbols) {
// The symbols were already at the top of the stack, do nothing!
// This should be quite common due to the structure of the Mono IR
return;
}
for sym in symbols.iter() {
let storage = self.get(sym).to_owned();
/// Load a single symbol using the C Calling Convention
/// *Private* because external code should always load symbols in bulk (see load_symbols)
fn load_symbol_ccc(&mut self, code_builder: &mut CodeBuilder, sym: Symbol) {
let storage = self.get(&sym).to_owned();
match storage {
StoredValue::VirtualMachineStack {
vm_state,
@ -212,13 +205,12 @@ impl<'a> Storage<'a> {
size,
} => {
let next_local_id = self.get_next_local_id();
let maybe_next_vm_state =
code_builder.load_symbol(*sym, vm_state, next_local_id);
let maybe_next_vm_state = code_builder.load_symbol(sym, vm_state, next_local_id);
match maybe_next_vm_state {
// The act of loading the value changed the VM state, so update it
Some(next_vm_state) => {
self.symbol_storage_map.insert(
*sym,
sym,
StoredValue::VirtualMachineStack {
vm_state: next_vm_state,
value_type,
@ -231,7 +223,7 @@ impl<'a> Storage<'a> {
// it was not in a convenient position in the VM stack.
self.local_types.push(value_type);
self.symbol_storage_map.insert(
*sym,
sym,
StoredValue::Local {
local_id: next_local_id,
value_type,
@ -241,24 +233,88 @@ impl<'a> Storage<'a> {
}
}
}
StoredValue::Local { local_id, .. }
| StoredValue::StackMemory {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
StoredValue::Local { local_id, .. } => {
code_builder.get_local(local_id);
code_builder.set_top_symbol(*sym);
code_builder.set_top_symbol(sym);
}
StoredValue::StackMemory {
location: StackMemoryLocation::FrameOffset(offset),
..
} => {
code_builder.get_local(self.stack_frame_pointer.unwrap());
StoredValue::StackMemory { location, .. } => {
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
code_builder.get_local(local_id);
if offset != 0 {
code_builder.i32_const(offset as i32);
code_builder.i32_add();
code_builder.set_top_symbol(*sym);
}
code_builder.set_top_symbol(sym);
}
}
}
/// Load symbols to the top of the VM stack
/// Avoid calling this method in a loop with one symbol at a time! It will work,
/// but it generates very inefficient Wasm code.
pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) {
if code_builder.verify_stack_match(symbols) {
// The symbols were already at the top of the stack, do nothing!
// This should be quite common due to the structure of the Mono IR
return;
}
for sym in symbols.iter() {
self.load_symbol_ccc(code_builder, *sym);
}
}
/// Load symbols in a way compatible with LLVM's "fast calling convention"
/// A bug in Zig means it always uses this for Wasm even when we specify C calling convention.
/// It squashes small structs into primitive values where possible, avoiding stack memory
/// in favour of CPU registers (or VM stack values, which eventually become CPU registers).
/// We need to convert some of our structs from our internal C-like representation to work with Zig.
/// We are sticking to C ABI for better compatibility on the platform side.
pub fn load_symbols_fastcc(
&mut self,
code_builder: &mut CodeBuilder,
symbols: &[Symbol],
return_symbol: Symbol,
return_layout: &WasmLayout,
) {
// Note: we are not doing verify_stack_match in this case so we may generate more code.
// We would need more bookkeeping in CodeBuilder to track which representation is on the stack!
if return_layout.is_stack_memory() {
// Load the address where the return value should be written
// Apparently for return values we still use a pointer to stack memory
self.load_symbol_ccc(code_builder, return_symbol);
};
for sym in symbols {
if let StoredValue::StackMemory {
location,
size,
alignment_bytes,
} = self.get(sym)
{
if *size == 0 {
unimplemented!("Passing zero-sized values is not implemented yet");
} else if *size > 8 {
return self.load_symbol_ccc(code_builder, *sym);
}
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
code_builder.get_local(local_id);
let align = Align::from(*alignment_bytes);
if *size == 1 {
code_builder.i32_load8_u(align, offset);
} else if *size == 2 {
code_builder.i32_load16_u(align, offset);
} else if *size <= 4 {
code_builder.i32_load(align, offset);
} else {
code_builder.i64_load(align, offset);
}
} else {
self.load_symbol_ccc(code_builder, *sym);
}
}
}

View file

@ -9,7 +9,7 @@ use super::opcodes::{OpCode, OpCode::*};
use super::serialize::{SerialBuffer, Serialize};
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
const ENABLE_DEBUG_LOG: bool = true;
const ENABLE_DEBUG_LOG: bool = false;
macro_rules! log_instruction {
($($x: expr),+) => {
if ENABLE_DEBUG_LOG { println!($($x,)*); }
@ -572,11 +572,14 @@ impl<'a> CodeBuilder<'a> {
);
}
/// Insert a linker relocation for a memory address
pub fn insert_memory_relocation(&mut self, symbol_index: u32) {
/// Insert a const reference to a memory address
pub fn i32_const_mem_addr(&mut self, addr: u32, symbol_index: u32) {
self.inst_base(I32CONST, 0, true);
let offset = self.code.len() as u32;
self.code.encode_padded_u32(addr);
self.relocations.push(RelocationEntry::Offset {
type_id: OffsetRelocType::MemoryAddrLeb,
offset: self.code.len() as u32,
offset,
symbol_index,
addend: 0,
});

View file

@ -29,6 +29,8 @@ pub enum SectionId {
Element = 9,
Code = 10,
Data = 11,
/// DataCount section is unused. Only needed for single-pass validation of
/// memory.init and data.drop, which we don't use
DataCount = 12,
}
@ -239,8 +241,7 @@ impl<'a> Serialize for ImportSection<'a> {
#[derive(Debug)]
pub struct FunctionSection<'a> {
/// Private. See WasmModule::add_function_signature
signature_indices: Vec<'a, u32>,
pub signature_indices: Vec<'a, u32>,
}
impl<'a> FunctionSection<'a> {
@ -525,42 +526,6 @@ impl Serialize for DataSection<'_> {
}
}
/*******************************************************************
*
* Data Count section
*
* Pre-declares the number of segments in the Data section.
* This helps the runtime to validate the module in a single pass.
* The order of sections is DataCount -> Code -> Data
*
*******************************************************************/
#[derive(Debug)]
struct DataCountSection {
count: u32,
}
impl DataCountSection {
fn new(data_section: &DataSection<'_>) -> Self {
let count = data_section
.segments
.iter()
.filter(|seg| !seg.init.is_empty())
.count() as u32;
DataCountSection { count }
}
}
impl Serialize for DataCountSection {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if self.count > 0 {
let header_indices = write_section_header(buffer, SectionId::DataCount);
buffer.encode_u32(self.count);
update_section_size(buffer, header_indices);
}
}
}
/*******************************************************************
*
* Module
@ -658,11 +623,6 @@ impl<'a> WasmModule<'a> {
counter.serialize_and_count(buffer, &self.start);
counter.serialize_and_count(buffer, &self.element);
// Data Count section forward-declares the size of the Data section
// so that Code section can be validated in one pass
let data_count_section = DataCountSection::new(&self.data);
counter.serialize_and_count(buffer, &data_count_section);
// Code section is the only one with relocations so we can stop counting
let code_section_index = counter.section_index;
let code_section_body_index = self

View file

@ -18,7 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
roc_reporting = { path = "../../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -1074,6 +1074,8 @@ define_builtins! {
49 LIST_SUBLIST: "sublist"
50 LIST_INTERSPERSE: "intersperse"
51 LIST_INTERSPERSE_CLOS: "#intersperseClos"
52 LIST_SPLIT: "split"
53 LIST_SPLIT_CLOS: "#splitClos"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -17,3 +17,5 @@ pretty_assertions = "1.0.0"
indoc = "1.0.3"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
diff = "0.1.12"
ansi_term = "0.12.1"

View file

@ -240,6 +240,9 @@ pub enum TypeAnnotation<'a> {
tags: Collection<'a, Loc<Tag<'a>>>,
},
/// '_', indicating the compiler should infer the type
Inferred,
/// The `*` type variable, e.g. in (List *)
Wildcard,

View file

@ -626,6 +626,7 @@ pub enum EType<'a> {
TApply(ETypeApply, Row, Col),
TBadTypeVariable(Row, Col),
TWildcard(Row, Col),
TInferred(Row, Col),
///
TStart(Row, Col),
TEnd(Row, Col),

View file

@ -55,6 +55,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETy
and!(
one_of!(
loc_wildcard(),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(EType::TRecord, record_type(min_indent))),
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),
@ -111,6 +112,15 @@ fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>>
})
}
/// The `_` indicating an inferred type, e.g. in (List _)
fn loc_inferred<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'_', EType::TInferred)), |loc_val: Located<
(),
>| {
loc_val.map(|_| TypeAnnotation::Inferred)
})
}
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
use crate::ast::Spaceable;
@ -119,6 +129,7 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentStart)),
one_of!(
loc_wildcard(),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(EType::TRecord, record_type(min_indent))),
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),

View file

@ -0,0 +1,14 @@
BinOps(
[
(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "x",
},
|L 0-0, C 2-3| Plus,
),
],
|L 0-0, C 4-5| Num(
"2",
),
)

View file

@ -0,0 +1 @@
x + 2

View file

@ -0,0 +1,13 @@
BinOps(
[
(
|L 0-0, C 0-1| Num(
"1",
),
|L 0-0, C 3-4| Plus,
),
],
|L 0-0, C 7-8| Num(
"2",
),
)

View file

@ -0,0 +1 @@
1 + 2

View file

@ -0,0 +1,14 @@
Apply(
|L 0-0, C 0-4| GlobalTag(
"Whee",
),
[
|L 0-0, C 5-7| Num(
"12",
),
|L 0-0, C 8-10| Num(
"34",
),
],
Space,
)

View file

@ -0,0 +1 @@
Whee 12 34

View file

@ -0,0 +1,18 @@
Apply(
|L 0-0, C 0-4| GlobalTag(
"Whee",
),
[
|L 0-0, C 6-8| ParensAround(
Num(
"12",
),
),
|L 0-0, C 11-13| ParensAround(
Num(
"34",
),
),
],
Space,
)

View file

@ -0,0 +1 @@
Whee (12) (34)

View file

@ -0,0 +1,14 @@
Apply(
|L 0-0, C 0-5| PrivateTag(
"@Whee",
),
[
|L 0-0, C 6-8| Num(
"12",
),
|L 0-0, C 9-11| Num(
"34",
),
],
Space,
)

View file

@ -0,0 +1 @@
@Whee 12 34

View file

@ -0,0 +1,21 @@
Apply(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "a",
},
[
|L 0-0, C 2-3| Var {
module_name: "",
ident: "b",
},
|L 0-0, C 4-5| Var {
module_name: "",
ident: "c",
},
|L 0-0, C 6-7| Var {
module_name: "",
ident: "d",
},
],
Space,
)

View file

@ -0,0 +1 @@
a b c d

View file

@ -0,0 +1,15 @@
Apply(
|L 0-0, C 0-4| Var {
module_name: "",
ident: "whee",
},
[
|L 0-0, C 6-8| Num(
"12",
),
|L 0-0, C 10-12| Num(
"34",
),
],
Space,
)

View file

@ -0,0 +1 @@
whee 12 34

View file

@ -0,0 +1,19 @@
Apply(
|L 0-0, C 0-5| UnaryOp(
|L 0-0, C 1-5| Var {
module_name: "",
ident: "whee",
},
|L 0-0, C 0-1| Negate,
),
[
|L 0-0, C 7-9| Num(
"12",
),
|L 0-0, C 10-13| Var {
module_name: "",
ident: "foo",
},
],
Space,
)

View file

@ -0,0 +1 @@
-whee 12 foo

View file

@ -0,0 +1,19 @@
Apply(
|L 0-0, C 0-5| UnaryOp(
|L 0-0, C 1-5| Var {
module_name: "",
ident: "whee",
},
|L 0-0, C 0-1| Not,
),
[
|L 0-0, C 7-9| Num(
"12",
),
|L 0-0, C 10-13| Var {
module_name: "",
ident: "foo",
},
],
Space,
)

View file

@ -0,0 +1 @@
!whee 12 foo

View file

@ -0,0 +1,12 @@
Apply(
|L 0-0, C 0-4| Var {
module_name: "",
ident: "whee",
},
[
|L 0-0, C 5-6| Num(
"1",
),
],
Space,
)

View file

@ -0,0 +1 @@
whee 1

View file

@ -0,0 +1,43 @@
SpaceBefore(
Defs(
[
|L 6-6, C 0-5| Body(
|L 6-6, C 0-1| Identifier(
"x",
),
|L 6-6, C 4-5| Num(
"5",
),
),
],
|L 8-8, C 0-2| SpaceBefore(
Num(
"42",
),
[
Newline,
Newline,
],
),
),
[
DocComment(
"first line of docs",
),
DocComment(
" second line",
),
DocComment(
" third line",
),
DocComment(
"fourth line",
),
DocComment(
"",
),
DocComment(
"sixth line after doc new line",
),
],
)

View file

@ -0,0 +1,9 @@
## first line of docs
## second line
## third line
## fourth line
##
## sixth line after doc new line
x = 5
42

View file

@ -0,0 +1,7 @@
Access(
Var {
module_name: "",
ident: "rec",
},
"field",
)

View file

@ -0,0 +1 @@
rec.field

View file

@ -0,0 +1,3 @@
GlobalTag(
"Whee",
)

View file

@ -0,0 +1 @@
Whee

View file

@ -0,0 +1,3 @@
PrivateTag(
"@Whee",
)

View file

@ -0,0 +1 @@
@Whee

View file

@ -0,0 +1,4 @@
Var {
module_name: "",
ident: "whee",
}

View file

@ -0,0 +1 @@
whee

View file

@ -0,0 +1,13 @@
Closure(
[
|L 0-0, C 1-2| Underscore(
"",
),
|L 0-0, C 4-9| Underscore(
"name",
),
],
|L 0-0, C 13-15| Num(
"42",
),
)

View file

@ -0,0 +1 @@
\_, _name -> 42

View file

@ -0,0 +1,20 @@
BinOps(
[
(
|L 0-0, C 0-2| Num(
"12",
),
|L 0-0, C 4-5| Star,
),
],
|L 1-1, C 1-3| SpaceBefore(
Num(
"92",
),
[
LineComment(
" test!",
),
],
),
)

View file

@ -0,0 +1,2 @@
12 * # test!
92

View file

@ -0,0 +1,20 @@
BinOps(
[
(
|L 0-0, C 0-1| SpaceAfter(
Num(
"3",
),
[
LineComment(
" test!",
),
],
),
|L 1-1, C 0-1| Plus,
),
],
|L 1-1, C 2-3| Num(
"4",
),
)

View file

@ -0,0 +1,2 @@
3 # test!
+ 4

View file

@ -0,0 +1,10 @@
List(
Collection {
items: [],
final_comments: [
LineComment(
"comment",
),
],
},
)

View file

@ -0,0 +1,2 @@
[#comment
]

View file

@ -0,0 +1,20 @@
BinOps(
[
(
|L 0-0, C 0-1| SpaceAfter(
Num(
"3",
),
[
LineComment(
" 2 × 2",
),
],
),
|L 1-1, C 0-1| Plus,
),
],
|L 1-1, C 2-3| Num(
"4",
),
)

View file

@ -0,0 +1,2 @@
3 # 2 × 2
+ 4

View file

@ -0,0 +1,23 @@
App {
header: AppHeader {
name: |L 0-0, C 4-14| PlainLine(
"test-app",
),
packages: [],
imports: [],
provides: [],
to: |L 0-0, C 53-57| ExistingPackage(
"blah",
),
before_header: [],
after_app_keyword: [],
before_packages: [],
after_packages: [],
before_imports: [],
after_imports: [],
before_provides: [],
after_provides: [],
before_to: [],
after_to: [],
},
}

View file

@ -0,0 +1 @@
app "test-app" packages {} imports [] provides [] to blah

View file

@ -0,0 +1,15 @@
Interface {
header: InterfaceHeader {
name: |L 0-0, C 10-13| ModuleName(
"Foo",
),
exposes: [],
imports: [],
before_header: [],
after_interface_keyword: [],
before_exposes: [],
after_exposes: [],
before_imports: [],
after_imports: [],
},
}

View file

@ -0,0 +1 @@
interface Foo exposes [] imports []

View file

@ -0,0 +1,3 @@
List(
[],
)

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1,43 @@
Platform {
header: PlatformHeader {
name: |L 0-0, C 9-23| PackageName {
account: "rtfeldman",
pkg: "blah",
},
requires: PlatformRequires {
rigids: [],
signature: |L 0-0, C 38-47| Entry {
ident: |L 0-0, C 38-42| "main",
spaces_before_colon: [],
ann: |L 0-0, C 45-47| Record {
fields: [],
ext: None,
},
},
},
exposes: [],
packages: [],
imports: [],
provides: [],
effects: Effects {
spaces_before_effects_keyword: [],
spaces_after_effects_keyword: [],
spaces_after_type_name: [],
effect_shortname: "fx",
effect_type_name: "Blah",
entries: [],
},
before_header: [],
after_platform_keyword: [],
before_requires: [],
after_requires: [],
before_exposes: [],
after_exposes: [],
before_packages: [],
after_packages: [],
before_imports: [],
after_imports: [],
before_provides: [],
after_provides: [],
},
}

View file

@ -0,0 +1 @@
platform rtfeldman/blah requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {}

View file

@ -0,0 +1,3 @@
Record(
[],
)

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,5 @@
Str(
PlainLine(
"",
),
)

View file

@ -0,0 +1 @@
""

View file

@ -0,0 +1,15 @@
BinOps(
[
(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "x",
},
|L 0-0, C 1-3| Equals,
),
],
|L 0-0, C 3-4| Var {
module_name: "",
ident: "y",
},
)

View file

@ -0,0 +1 @@
x==y

View file

@ -0,0 +1,15 @@
BinOps(
[
(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "x",
},
|L 0-0, C 2-4| Equals,
),
],
|L 0-0, C 5-6| Var {
module_name: "",
ident: "y",
},
)

View file

@ -0,0 +1 @@
x == y

View file

@ -0,0 +1,24 @@
Expect(
|L 0-0, C 7-13| BinOps(
[
(
|L 0-0, C 7-8| Num(
"1",
),
|L 0-0, C 9-11| Equals,
),
],
|L 0-0, C 12-13| Num(
"1",
),
),
|L 2-2, C 0-1| SpaceBefore(
Num(
"4",
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
expect 1 == 1
4

View file

@ -0,0 +1,3 @@
Float(
"-1_23_456.0_1_23_456",
)

View file

@ -0,0 +1 @@
-1_23_456.0_1_23_456

View file

@ -0,0 +1,51 @@
App {
header: AppHeader {
name: |L 0-0, C 4-15| PlainLine(
"quicksort",
),
packages: [
|L 1-1, C 15-33| Entry {
shorthand: "base",
spaces_after_shorthand: [],
package_or_path: |L 1-1, C 21-33| Path(
PlainLine(
"./platform",
),
),
},
],
imports: [
|L 2-2, C 14-25| Package(
"foo",
ModuleName(
"Bar.Baz",
),
[],
),
],
provides: [
|L 3-3, C 15-24| Exposed(
"quicksort",
),
],
to: |L 3-3, C 30-34| ExistingPackage(
"base",
),
before_header: [],
after_app_keyword: [],
before_packages: [
Newline,
],
after_packages: [],
before_imports: [
Newline,
],
after_imports: [],
before_provides: [
Newline,
],
after_provides: [],
before_to: [],
after_to: [],
},
}

View file

@ -0,0 +1,4 @@
app "quicksort"
packages { base: "./platform" }
imports [ foo.Bar.Baz ]
provides [ quicksort ] to base

View file

@ -0,0 +1,76 @@
App {
header: AppHeader {
name: |L 0-0, C 4-15| PlainLine(
"quicksort",
),
packages: [
|L 1-1, C 15-33| Entry {
shorthand: "base",
spaces_after_shorthand: [],
package_or_path: |L 1-1, C 21-33| Path(
PlainLine(
"./platform",
),
),
},
],
imports: [
|L 2-6, C 14-5| Package(
"foo",
ModuleName(
"Bar",
),
Collection {
items: [
|L 3-3, C 8-11| SpaceBefore(
Exposed(
"Baz",
),
[
Newline,
],
),
|L 4-4, C 8-16| SpaceBefore(
Exposed(
"FortyTwo",
),
[
Newline,
],
),
],
final_comments: [
Newline,
LineComment(
" I'm a happy comment",
),
],
},
),
],
provides: [
|L 7-7, C 15-24| Exposed(
"quicksort",
),
],
to: |L 7-7, C 31-35| ExistingPackage(
"base",
),
before_header: [],
after_app_keyword: [],
before_packages: [
Newline,
],
after_packages: [],
before_imports: [
Newline,
],
after_imports: [],
before_provides: [
Newline,
],
after_provides: [],
before_to: [],
after_to: [],
},
}

View file

@ -0,0 +1,8 @@
app "quicksort"
packages { base: "./platform", }
imports [ foo.Bar.{
Baz,
FortyTwo,
# I'm a happy comment
} ]
provides [ quicksort, ] to base

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