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

View file

@ -28,24 +28,7 @@ inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
# 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" ] }
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10"
[dev-dependencies]

View file

@ -100,20 +100,22 @@ pub fn gen_from_mono_module(
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
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) {
let name = function.get_name().to_str().unwrap();
// mark our zig-defined builtins as internal
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
}
if name.starts_with("roc_builtins.dict") || name.starts_with("dict.RocDict") {
function.add_attribute(AttributeLoc::Function, attr);
}
if name.starts_with("roc_builtins.list") || name.starts_with("list.RocList") {
function.add_attribute(AttributeLoc::Function, attr);
if name.starts_with("roc_builtins.dict")
|| name.starts_with("dict.RocDict")
|| name.starts_with("roc_builtins.list")
|| name.starts_with("list.RocList")
{
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,15 +10,29 @@ use std::str;
fn main() {
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
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
let src_obj_path = bitcode_path.join("builtins.o");
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
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"]);
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 = dest_obj_path.to_str().expect("Invalid dest object path");
@ -26,12 +40,6 @@ fn main() {
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 = dest_bc_path.to_str().expect("Invalid dest bc path");
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<()> {
if dir.is_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"] }
inlinable_string = "0.1"
either = "1.6.1"
# 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" ] }
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10"
[dev-dependencies]

View file

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

View file

@ -5928,35 +5928,48 @@ fn reuse_function_symbol<'a>(
) -> Stmt<'a> {
match procs.partial_procs.get(&original) {
None => {
let is_imported = env.is_imported_symbol(original);
match arg_var {
Some(arg_var) if is_imported => {
Some(arg_var) if env.is_imported_symbol(original) => {
let layout = layout_cache
.from_var(env.arena, arg_var, env.subs)
.expect("creating layout does not fail");
let top_level = TopLevelFunctionLayout::from_layout(env.arena, layout);
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,
);
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);
procs.insert_passed_by_name(
env,
arg_var,
original,
top_level,
layout_cache,
);
// 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));
let_empty_struct(symbol, env.arena.alloc(result))
}
}
_ => {
// danger: a foreign symbol may not be specialized!
debug_assert!(
!is_imported,
!env.is_imported_symbol(original),
"symbol {:?} while processing module {:?}",
original,
(env.home, &arg_var),
);
result
}
}
result
}
Some(partial_proc) => {

View file

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

View file

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

View file

@ -30,24 +30,7 @@ inlinable_string = "0.1"
either = "1.6.1"
indoc = "0.3.3"
libc = "0.2"
# 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" ] }
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10"
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()
}