Merge branch 'trunk' into morphic-static-strings

This commit is contained in:
Folkert de Vries 2021-05-30 23:03:29 +02:00 committed by GitHub
commit d263016a84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 917 additions and 589 deletions

2
.envrc
View file

@ -93,7 +93,7 @@ use_nix() {
fi fi
log_status "use nix: updating cache" log_status "use nix: updating cache"
nix-shell --pure "${drv}" --show-trace --run "$(join_args "$direnv" dump bash)" > "${dump}" nix-shell "${drv}" --show-trace --run "$(join_args "$direnv" dump bash)" > "${dump}"
if [[ "${?}" -ne 0 ]] || [[ ! -f "${dump}" ]] || ! grep -q IN_NIX_SHELL "${dump}"; then if [[ "${?}" -ne 0 ]] || [[ ! -f "${dump}" ]] || ! grep -q IN_NIX_SHELL "${dump}"; then
rm -rf "${wd}" rm -rf "${wd}"
fail "use nix: was not able to update the cache of the environment. Please run 'direnv reload' to try again." fail "use nix: was not able to update the cache of the environment. Please run 'direnv reload' to try again."

View file

@ -20,7 +20,7 @@ For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir).
### libunwind & libc++-dev ### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be donw with `sudo apt-get install libunwind-dev`). MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be done with `sudo apt-get install libunwind-dev`).
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
### libcxb libraries ### libcxb libraries

391
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,8 @@ members = [
"compiler/reporting", "compiler/reporting",
"compiler/fmt", "compiler/fmt",
"compiler/mono", "compiler/mono",
"compiler/test_mono_macros",
"compiler/test_mono",
"compiler/load", "compiler/load",
"compiler/gen", "compiler/gen",
"compiler/gen_dev", "compiler/gen_dev",
@ -21,6 +23,7 @@ members = [
"compiler/arena_pool", "compiler/arena_pool",
"compiler/test_gen", "compiler/test_gen",
"vendor/ena", "vendor/ena",
"vendor/inkwell",
"vendor/pathfinding", "vendor/pathfinding",
"vendor/pretty", "vendor/pretty",
"editor", "editor",

View file

@ -62,24 +62,7 @@ inlinable_string = "0.1"
libc = "0.2" libc = "0.2"
libloading = "0.6" libloading = "0.6"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. inkwell = { path = "../vendor/inkwell" }
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release4", features = [ "llvm10-0" ] }
target-lexicon = "0.10" target-lexicon = "0.10"
tempfile = "3.1.0" tempfile = "3.1.0"

View file

@ -28,24 +28,7 @@ inlinable_string = "0.1.0"
libloading = "0.6" libloading = "0.6"
tempfile = "3.1.0" tempfile = "3.1.0"
serde_json = "1.0" serde_json = "1.0"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. inkwell = { path = "../../vendor/inkwell" }
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release4", features = [ "llvm10-0" ] }
target-lexicon = "0.10" target-lexicon = "0.10"
[dev-dependencies] [dev-dependencies]

View file

@ -100,20 +100,22 @@ pub fn gen_from_mono_module(
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0); debug_assert!(kind_id > 0);
let attr = context.create_enum_attribute(kind_id, 1); let enum_attr = context.create_enum_attribute(kind_id, 1);
for function in FunctionIterator::from_module(module) { for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap(); let name = function.get_name().to_str().unwrap();
// mark our zig-defined builtins as internal
if name.starts_with("roc_builtins") { if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal); function.set_linkage(Linkage::Internal);
} }
if name.starts_with("roc_builtins.dict") || name.starts_with("dict.RocDict") { if name.starts_with("roc_builtins.dict")
function.add_attribute(AttributeLoc::Function, attr); || name.starts_with("dict.RocDict")
} || name.starts_with("roc_builtins.list")
|| name.starts_with("list.RocList")
if name.starts_with("roc_builtins.list") || name.starts_with("list.RocList") { {
function.add_attribute(AttributeLoc::Function, attr); function.add_attribute(AttributeLoc::Function, enum_attr);
} }
} }

View file

@ -0,0 +1,5 @@
#!/bin/bash
set -euxo pipefail
zig build-obj src/main.zig -O ReleaseFast -femit-llvm-ir=builtins.ll -femit-bin=builtins.o --strip

View file

@ -10,28 +10,36 @@ use std::str;
fn main() { fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = env::var_os("OUT_DIR").unwrap();
let big_sur_path = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib";
let use_build_script = Path::new(big_sur_path).exists();
// "." is relative to where "build.rs" is // "." is relative to where "build.rs" is
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap(); let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode"); let bitcode_path = build_script_dir_path.join("bitcode");
let src_obj_path = bitcode_path.join("builtins.o"); let src_obj_path = bitcode_path.join("builtins.o");
let src_obj = src_obj_path.to_str().expect("Invalid src object path"); let src_obj = src_obj_path.to_str().expect("Invalid src object path");
println!("Compiling zig object to: {}", src_obj);
let dest_ir_path = bitcode_path.join("builtins.ll");
let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path");
if use_build_script {
println!("Compiling zig object & ir to: {} and {}", src_obj, dest_ir);
run_command_with_no_args(&bitcode_path, "./build.sh");
} else {
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]); run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
println!("Compiling ir to: {}", dest_ir);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
}
let dest_obj_path = Path::new(&out_dir).join("builtins.o"); let dest_obj_path = Path::new(&out_dir).join("builtins.o");
let dest_obj = dest_obj_path.to_str().expect("Invalid dest object path"); let dest_obj = dest_obj_path.to_str().expect("Invalid dest object path");
println!("Moving zig object to: {}", dest_obj); println!("Moving zig object to: {}", dest_obj);
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]); run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
let dest_ir_path = bitcode_path.join("builtins.ll");
let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path");
println!("Compiling ir to: {}", dest_ir);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
let dest_bc_path = bitcode_path.join("builtins.bc"); let dest_bc_path = bitcode_path.join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path"); let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling bitcode to: {}", dest_bc); println!("Compiling bitcode to: {}", dest_bc);
@ -78,6 +86,25 @@ where
} }
} }
fn run_command_with_no_args<P: AsRef<Path>>(path: P, command_str: &str) {
let output_result = Command::new(OsStr::new(&command_str))
.current_dir(path)
.output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
panic!("{} failed: {}", command_str, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> { fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() { if dir.is_dir() {
for entry in fs::read_dir(dir)? { for entry in fs::read_dir(dir)? {

View file

@ -20,24 +20,7 @@ im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
either = "1.6.1" either = "1.6.1"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. inkwell = { path = "../../vendor/inkwell" }
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release4", features = [ "llvm10-0" ] }
target-lexicon = "0.10" target-lexicon = "0.10"
[dev-dependencies] [dev-dependencies]

View file

@ -5791,7 +5791,7 @@ fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> Function
None => { None => {
let personality_func = add_func( let personality_func = add_func(
module, module,
"__gxx_personality_v0", name,
context.i64_type().fn_type(&[], false), context.i64_type().fn_type(&[], false),
Linkage::External, Linkage::External,
C_CALL_CONV, C_CALL_CONV,
@ -5843,7 +5843,7 @@ fn cxa_begin_catch<'a, 'ctx, 'env>(
let cxa_begin_catch = add_func( let cxa_begin_catch = add_func(
module, module,
"__cxa_begin_catch", name,
u8_ptr.fn_type(&[u8_ptr.into()], false), u8_ptr.fn_type(&[u8_ptr.into()], false),
Linkage::External, Linkage::External,
C_CALL_CONV, C_CALL_CONV,

View file

@ -5928,36 +5928,49 @@ fn reuse_function_symbol<'a>(
) -> Stmt<'a> { ) -> Stmt<'a> {
match procs.partial_procs.get(&original) { match procs.partial_procs.get(&original) {
None => { None => {
let is_imported = env.is_imported_symbol(original);
match arg_var { match arg_var {
Some(arg_var) if is_imported => { Some(arg_var) if env.is_imported_symbol(original) => {
let layout = layout_cache let layout = layout_cache
.from_var(env.arena, arg_var, env.subs) .from_var(env.arena, arg_var, env.subs)
.expect("creating layout does not fail"); .expect("creating layout does not fail");
if procs.imported_module_thunks.contains(&original) {
let top_level = TopLevelFunctionLayout::new(env.arena, &[], layout);
procs.insert_passed_by_name(
env,
arg_var,
original,
top_level,
layout_cache,
);
force_thunk(env, original, layout, symbol, env.arena.alloc(result))
} else {
let top_level = TopLevelFunctionLayout::from_layout(env.arena, layout); let top_level = TopLevelFunctionLayout::from_layout(env.arena, layout);
procs.insert_passed_by_name(
env,
arg_var,
original,
top_level,
layout_cache,
);
procs.insert_passed_by_name(env, arg_var, original, top_level, layout_cache); let_empty_struct(symbol, env.arena.alloc(result))
}
// an imported symbol is either a function, or a top-level 0-argument thunk
// it never has closure data, so we use the empty struct
return let_empty_struct(symbol, env.arena.alloc(result));
} }
_ => { _ => {
// danger: a foreign symbol may not be specialized! // danger: a foreign symbol may not be specialized!
debug_assert!( debug_assert!(
!is_imported, !env.is_imported_symbol(original),
"symbol {:?} while processing module {:?}", "symbol {:?} while processing module {:?}",
original, original,
(env.home, &arg_var), (env.home, &arg_var),
); );
}
}
result result
} }
}
}
Some(partial_proc) => { Some(partial_proc) => {
let arg_var = arg_var.unwrap_or(partial_proc.annotation); let arg_var = arg_var.unwrap_or(partial_proc.annotation);

View file

@ -276,7 +276,8 @@ impl<'a> LambdaSet<'a> {
use UnionVariant::*; use UnionVariant::*;
match variant { match variant {
Never | Unit | UnitWithArguments => Layout::Struct(&[]), Never => Layout::Union(UnionLayout::NonRecursive(&[])),
Unit | UnitWithArguments => Layout::Struct(&[]),
BoolUnion { .. } => Layout::Builtin(Builtin::Int1), BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
ByteUnion(_) => Layout::Builtin(Builtin::Int8), ByteUnion(_) => Layout::Builtin(Builtin::Int8),
Unwrapped(_tag_name, layouts) => Layout::Struct(layouts.into_bump_slice()), Unwrapped(_tag_name, layouts) => Layout::Struct(layouts.into_bump_slice()),

View file

@ -270,11 +270,12 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
procedure Test.0 (): procedure Test.0 ():
let Test.11 = 1i64; let Test.12 = 1i64;
let Test.9 = 1i64; let Test.10 = 1i64;
let Test.10 = 2i64; let Test.11 = 2i64;
let Test.5 = These Test.11 Test.9 Test.10; let Test.5 = These Test.12 Test.10 Test.11;
switch Test.5: let Test.9 = Index 0 Test.5;
switch Test.9:
case 2: case 2:
let Test.2 = Index 1 Test.5; let Test.2 = Index 1 Test.5;
ret Test.2; ret Test.2;

View file

@ -30,24 +30,7 @@ inlinable_string = "0.1"
either = "1.6.1" either = "1.6.1"
indoc = "0.3.3" indoc = "0.3.3"
libc = "0.2" libc = "0.2"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. inkwell = { path = "../../vendor/inkwell" }
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release4", features = [ "llvm10-0" ] }
target-lexicon = "0.10" target-lexicon = "0.10"
libloading = "0.6" libloading = "0.6"

View file

@ -0,0 +1,40 @@
[package]
name = "test_mono"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_reporting = { path = "../reporting" }
roc_load = { path = "../load" }
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_build = { path = "../build" }
roc_mono = { path = "../mono" }
test_mono_macros = { path = "../test_mono_macros" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
either = "1.6.1"
indoc = "0.3.3"
libc = "0.2"
target-lexicon = "0.10"
libloading = "0.6"
[dev-dependencies]
pretty_assertions = "0.5.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
bumpalo = { version = "3.6.1", features = ["collections"] }

View file

@ -0,0 +1,25 @@
procedure List.7 (#Attr.2):
let Test.6 = lowlevel ListLen #Attr.2;
ret Test.6;
procedure Num.24 (#Attr.2, #Attr.3):
let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.5;
procedure Test.0 ():
let Test.11 = 1i64;
let Test.12 = 2i64;
let Test.1 = Array [Test.11, Test.12];
let Test.9 = 5i64;
let Test.10 = 4i64;
invoke Test.7 = CallByName Num.24 Test.9 Test.10 catch
dec Test.1;
unreachable;
let Test.8 = 3i64;
invoke Test.3 = CallByName Num.24 Test.7 Test.8 catch
dec Test.1;
unreachable;
let Test.4 = CallByName List.7 Test.1;
dec Test.1;
let Test.2 = CallByName Num.24 Test.3 Test.4;
ret Test.2;

View file

@ -0,0 +1,3 @@
procedure Test.0 ():
let Test.1 = 5i64;
ret Test.1;

View file

@ -0,0 +1,184 @@
#![cfg(test)]
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
// we actually want to compare against the literal float bits
#![allow(clippy::clippy::float_cmp)]
#[macro_use]
extern crate pretty_assertions;
use test_mono_macros::*;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_mono::ir::Proc;
use roc_mono::ir::TopLevelFunctionLayout;
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
fn compiles_to_ir(test_name: &str, src: &str) {
use bumpalo::Bump;
use std::path::{Path, PathBuf};
let arena = &Bump::new();
// let stdlib = roc_builtins::unique::uniq_stdlib();
let stdlib = roc_builtins::std::standard_stdlib();
let filename = PathBuf::from("Test.roc");
let src_dir = Path::new("fake/test/path");
let module_src;
let temp;
if src.starts_with("app") {
// this is already a module
module_src = src;
} else {
// this is an expression, promote it to a module
temp = promote_expr_to_module(src);
module_src = &temp;
}
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
arena,
filename,
&module_src,
&stdlib,
src_dir,
exposed_types,
8,
builtin_defs_map,
);
let mut loaded = match loaded {
Ok(x) => x,
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
println!("{}", report);
panic!();
}
Err(e) => panic!("{:?}", e),
};
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
module_id: home,
procedures,
exposed_to_host,
..
} = loaded;
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
if !can_problems.is_empty() {
println!("Ignoring {} canonicalization problems", can_problems.len());
}
assert_eq!(type_problems, Vec::new());
assert_eq!(mono_problems, Vec::new());
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().next().unwrap();
verify_procedures(test_name, procedures, main_fn_symbol);
}
#[cfg(debug_assertions)]
fn verify_procedures(
test_name: &str,
procedures: MutMap<(Symbol, TopLevelFunctionLayout<'_>), Proc<'_>>,
main_fn_symbol: Symbol,
) {
let index = procedures
.keys()
.position(|(s, _)| *s == main_fn_symbol)
.unwrap();
let mut procs_string = procedures
.values()
.map(|proc| proc.to_pretty(200))
.collect::<Vec<_>>();
let main_fn = procs_string.swap_remove(index);
procs_string.sort();
procs_string.push(main_fn);
let result = procs_string.join("\n");
let path = format!("generated/{}.txt", test_name);
std::fs::create_dir_all("generated").unwrap();
std::fs::write(&path, result).unwrap();
use std::process::Command;
let is_tracked = Command::new("git")
.args(&["ls-files", "--error-unmatch", &path])
.output()
.unwrap();
if !is_tracked.status.success() {
panic!(
"The file {:?} is not tracked by git. Try using `git add` on it",
&path
);
}
let has_changes = Command::new("git")
.args(&["diff", "--color=always", &path])
.output()
.unwrap();
if !has_changes.status.success() {
eprintln!("`git diff {:?}` failed", &path);
unreachable!();
}
if !has_changes.stdout.is_empty() {
println!("{}", std::str::from_utf8(&has_changes.stdout).unwrap());
panic!("Output changed: resolve conflicts and `git add` the file.");
}
}
// NOTE because the Show instance of module names is different in --release mode,
// these tests would all fail. In the future, when we do interesting optimizations,
// we'll likely want some tests for --release too.
#[cfg(not(debug_assertions))]
fn verify_procedures(
_expected: &str,
_procedures: MutMap<(Symbol, TopLevelFunctionLayout<'_>), Proc<'_>>,
_main_fn_symbol: Symbol,
) {
// Do nothing
}
#[mono_test]
fn ir_int_literal() {
r#"
5
"#
}
#[mono_test]
fn ir_int_add() {
r#"
x = [ 1,2 ]
5 + 4 + 3 + List.len x
"#
}

View file

@ -0,0 +1,15 @@
[package]
name = "test_mono_macros"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[lib]
proc-macro = true
[dependencies]
syn = { version = "1.0.39", features = ["full", "extra-traits"] }
quote = "1.0.7"
darling = "0.10.2"
proc-macro2 = "1.0.24"

View file

@ -0,0 +1,26 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_attribute]
pub fn mono_test(_args: TokenStream, item: TokenStream) -> TokenStream {
let task_fn = syn::parse_macro_input!(item as syn::ItemFn);
let args = task_fn.sig.inputs.clone();
let name = task_fn.sig.ident.clone();
let name_str = name.to_string();
let body = task_fn.block.clone();
let visibility = &task_fn.vis;
let result = quote! {
#[test]
#visibility fn #name(#args) -> () {
compiles_to_ir(#name_str, #body);
}
};
result.into()
}

View file

@ -121,7 +121,7 @@ pub enum Expr2 {
If { If {
cond_var: Variable, // 4B cond_var: Variable, // 4B
expr_var: Variable, // 4B expr_var: Variable, // 4B
branches: PoolVec<(Expr2, Expr2)>, // 8B branches: PoolVec<(NodeId<Expr2>, NodeId<Expr2>)>, // 8B
final_else: NodeId<Expr2>, // 4B final_else: NodeId<Expr2>, // 4B
}, },
When { When {

View file

@ -13,7 +13,7 @@ use roc_module::{ident::TagName, symbol::Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::{ use roc_types::{
subs::Variable, subs::Variable,
types, types::{self, AnnotationSource},
types::{Category, Reason}, types::{Category, Reason},
}; };
@ -479,6 +479,157 @@ pub fn constrain_expr<'a>(
exists(arena, flex_vars, And(and_constraints)) exists(arena, flex_vars, And(and_constraints))
} }
Expr2::If {
cond_var,
expr_var,
branches,
final_else,
} => {
let expect_bool = |region| {
let bool_type = Type2::Variable(Variable::BOOL);
Expected::ForReason(Reason::IfCondition, bool_type, region)
};
let mut branch_cons = BumpVec::with_capacity_in(2 * branches.len() + 3, arena);
// TODO why does this cond var exist? is it for error messages?
// let first_cond_region = branches[0].0.region;
let cond_var_is_bool_con = Eq(
Type2::Variable(*cond_var),
expect_bool(region),
Category::If,
region,
);
branch_cons.push(cond_var_is_bool_con);
let final_else_expr = env.pool.get(*final_else);
let mut flex_vars = BumpVec::with_capacity_in(2, arena);
flex_vars.push(*cond_var);
flex_vars.push(*expr_var);
match expected {
Expected::FromAnnotation(name, arity, _, tipe) => {
let num_branches = branches.len() + 1;
for (index, branch_id) in branches.iter_node_ids().enumerate() {
let (cond_id, body_id) = env.pool.get(branch_id);
let cond = env.pool.get(*cond_id);
let body = env.pool.get(*body_id);
let cond_con =
constrain_expr(arena, env, cond, expect_bool(region), region);
let then_con = constrain_expr(
arena,
env,
body,
Expected::FromAnnotation(
name.clone(),
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(index),
num_branches,
},
tipe.shallow_clone(),
),
region,
);
branch_cons.push(cond_con);
branch_cons.push(then_con);
}
let else_con = constrain_expr(
arena,
env,
final_else_expr,
Expected::FromAnnotation(
name,
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(branches.len()),
num_branches,
},
tipe.shallow_clone(),
),
region,
);
let ast_con = Eq(
Type2::Variable(*expr_var),
Expected::NoExpectation(tipe),
Category::Storage(std::file!(), std::line!()),
region,
);
branch_cons.push(ast_con);
branch_cons.push(else_con);
exists(arena, flex_vars, And(branch_cons))
}
_ => {
for (index, branch_id) in branches.iter_node_ids().enumerate() {
let (cond_id, body_id) = env.pool.get(branch_id);
let cond = env.pool.get(*cond_id);
let body = env.pool.get(*body_id);
let cond_con =
constrain_expr(arena, env, cond, expect_bool(region), region);
let then_con = constrain_expr(
arena,
env,
body,
Expected::ForReason(
Reason::IfBranch {
index: Index::zero_based(index),
total_branches: branches.len(),
},
Type2::Variable(*expr_var),
// should be from body
region,
),
region,
);
branch_cons.push(cond_con);
branch_cons.push(then_con);
}
let else_con = constrain_expr(
arena,
env,
final_else_expr,
Expected::ForReason(
Reason::IfBranch {
index: Index::zero_based(branches.len()),
total_branches: branches.len() + 1,
},
Type2::Variable(*expr_var),
// should come from final_else
region,
),
region,
);
branch_cons.push(Eq(
Type2::Variable(*expr_var),
expected,
Category::Storage(std::file!(), std::line!()),
region,
));
branch_cons.push(else_con);
exists(arena, flex_vars, And(branch_cons))
}
}
}
_ => todo!("implement constaints for {:?}", expr), _ => todo!("implement constaints for {:?}", expr),
} }
} }

View file

@ -516,7 +516,7 @@ pub fn to_expr2<'a>(
output.references.union_mut(cond_output.references); output.references.union_mut(cond_output.references);
output.references.union_mut(then_output.references); output.references.union_mut(then_output.references);
new_branches.push((cond, then_expr)); new_branches.push((env.pool.add(cond), env.pool.add(then_expr)));
} }
let (else_expr, else_output) = let (else_expr, else_output) =

View file

@ -274,3 +274,15 @@ fn constrain_access() {
"Str", "Str",
) )
} }
#[test]
fn constrain_if() {
infer_eq(
indoc!(
r#"
if True then Green else Red
"#
),
"[ Green, Red ]*",
)
}

View file

@ -1 +0,0 @@
main

View file

@ -1,7 +0,0 @@
app "main" imports [ Effect ] provides [ rocMain ] to "./platform"
rocMain : Effect.Effect {} as Fx
rocMain =
when List.len (Str.split "hello" "JJJJ there") is
_ -> Effect.putLine "Yay"

View file

@ -1,23 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View file

@ -1,14 +0,0 @@
[package]
name = "host"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View file

@ -1,15 +0,0 @@
platform folkertdev/foo
requires { rocMain : Effect {} }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects Effect
{
putChar : Int -> Effect {},
putLine : Str -> Effect {},
getLine : Effect Str
}
mainForHost : Effect {} as Fx
mainForHost = rocMain

View file

@ -1,7 +0,0 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View file

@ -1,147 +0,0 @@
#![allow(non_snake_case)]
use roc_std::alloca;
use roc_std::RocCallResult;
use roc_std::RocStr;
use std::alloc::Layout;
use std::ffi::c_void;
use std::time::SystemTime;
extern "C" {
#[link_name = "roc__rocMain_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "roc__rocMain_1_size"]
fn roc_main_size() -> i64;
#[link_name = "roc__rocMain_1_Fx_caller"]
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "roc__rocMain_1_Fx_size"]
fn size_Fx() -> i64;
fn malloc(size: usize) -> *mut c_void;
fn realloc(c_ptr: *mut c_void, size: usize) -> *mut c_void;
fn free(c_ptr: *mut c_void);
}
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return malloc(size);
}
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return free(c_ptr);
}
#[no_mangle]
pub fn roc_fx_putChar(foo: i64) -> () {
let character = foo as u8 as char;
print!("{}", character);
()
}
#[no_mangle]
pub fn roc_fx_putLine(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
()
}
#[no_mangle]
pub fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from_slice_with_capacity(line1.as_bytes(), line1.len())
}
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
let size = size_Fx() as usize;
alloca::with_stack_bytes(size, |buffer| {
let buffer: *mut std::ffi::c_void = buffer;
let buffer: *mut u8 = buffer as *mut u8;
call_Fx(
function_pointer,
closure_data_ptr as *const u8,
buffer as *mut u8,
);
let output = &*(buffer as *mut RocCallResult<i64>);
match output.into() {
Ok(_) => 0,
Err(e) => panic!("failed with {}", e),
}
})
}
#[no_mangle]
pub fn rust_main() -> isize {
println!("Running Roc closure");
let start_time = SystemTime::now();
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
let answer = unsafe {
let buffer = std::alloc::alloc(layout);
roc_main(buffer);
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(()) => {
let function_pointer = {
// this is a pointer to the location where the function pointer is stored
// we pass just the function pointer
let temp = buffer.offset(8) as *const i64;
(*temp) as *const u8
};
let closure_data_ptr = buffer.offset(16);
let result =
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
std::alloc::dealloc(buffer, layout);
result
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
panic!("Roc failed with message: {}", msg);
}
}
};
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc execution took {:.4} ms",
duration.as_secs_f64() * 1000.0,
);
// Exit code
0
}

View file

@ -4,15 +4,18 @@ let
splitSystem = builtins.split "-" builtins.currentSystem; splitSystem = builtins.split "-" builtins.currentSystem;
currentArch = builtins.elemAt splitSystem 0; currentArch = builtins.elemAt splitSystem 0;
currentOS = builtins.elemAt splitSystem 2; currentOS = builtins.elemAt splitSystem 2;
in with { in
with {
# Look here for information about how pin version of nixpkgs # Look here for information about how pin version of nixpkgs
# → https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs # → https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs
pkgs = import (builtins.fetchGit { pkgs = import (
name = "nixpkgs-2021-04-23"; builtins.fetchGit {
# name = "nixpkgs-2021-04-23";
url = "https://github.com/nixos/nixpkgs/"; url = "https://github.com/nixos/nixpkgs/";
ref = "refs/heads/nixpkgs-unstable"; ref = "refs/heads/nixpkgs-unstable";
rev = "8d0340aee5caac3807c58ad7fa4ebdbbdd9134d6"; rev = "8d0340aee5caac3807c58ad7fa4ebdbbdd9134d6";
}) { }; }
) {};
isMacOS = currentOS == "darwin"; isMacOS = currentOS == "darwin";
isLinux = currentOS == "linux"; isLinux = currentOS == "linux";
@ -53,13 +56,6 @@ let
else else
[]; [];
nixos-env =
if isLinux && builtins.pathExists /etc/nixos/configuration.nix then
{ XDG_DATA_DIRS = "/run/opengl-driver/share:$XDG_DATA_DIRS";
}
else
{ };
llvmPkgs = pkgs.llvmPackages_10; llvmPkgs = pkgs.llvmPackages_10;
zig = import ./nix/zig.nix { inherit pkgs isMacOS isAarch64; }; zig = import ./nix/zig.nix { inherit pkgs isMacOS isAarch64; };
inputs = [ inputs = [
@ -91,13 +87,16 @@ let
# (import ./nix/zls.nix { inherit pkgs zig; }) # (import ./nix/zls.nix { inherit pkgs zig; })
]; ];
in mkShell (nixos-env // { in
mkShell (
{
buildInputs = inputs ++ darwin-inputs ++ linux-inputs; buildInputs = inputs ++ darwin-inputs ++ linux-inputs;
# Additional Env vars # Additional Env vars
LLVM_SYS_100_PREFIX = "${llvmPkgs.llvm}"; LLVM_SYS_100_PREFIX = "${llvmPkgs.llvm}";
LD_LIBRARY_PATH = stdenv.lib.makeLibraryPath LD_LIBRARY_PATH = stdenv.lib.makeLibraryPath
([ (
[
pkg-config pkg-config
stdenv.cc.cc.lib stdenv.cc.cc.lib
llvmPkgs.libcxx llvmPkgs.libcxx
@ -106,11 +105,12 @@ in mkShell (nixos-env // {
libffi libffi
ncurses ncurses
zlib zlib
] ++ linux-inputs); ] ++ linux-inputs
);
# Aliases don't work cross shell, so we do this # Aliases don't work cross shell, so we do this
shellHook = '' shellHook = ''
export PATH="$PATH:$PWD/nix/bin" export PATH="$PATH:$PWD/nix/bin"
''; '';
}) }
)

31
vendor/inkwell/Cargo.toml vendored Normal file
View file

@ -0,0 +1,31 @@
[package]
name = "inkwell"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release4", features = [ "llvm10-0" ] }
[features]
target-arm = []
target-aarch64 = []
target-webassembly = []

4
vendor/inkwell/src/lib.rs vendored Normal file
View file

@ -0,0 +1,4 @@
#![cfg(not(doctest))]
// re-export all inkwell members. This way we can switch
// inkwell versions by making changes in just one place.
pub use inkwell::*;