Merge branch 'trunk' of github.com:rtfeldman/roc into docs-parse-code-blocks

This commit is contained in:
Chadtech 2021-08-29 15:57:19 -04:00
commit c53c5fdb33
75 changed files with 2791 additions and 708 deletions

6
Cargo.lock generated
View File

@ -3017,6 +3017,7 @@ dependencies = [
"roc_region",
"roc_reporting",
"roc_solve",
"roc_std",
"roc_types",
"roc_unify",
"serde_json",
@ -3365,6 +3366,7 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_solve",
"roc_std",
"roc_types",
"roc_unify",
"ven_ena",
@ -3917,9 +3919,9 @@ dependencies = [
[[package]]
name = "target-lexicon"
version = "0.10.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d"
checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff"
[[package]]
name = "tempfile"

View File

@ -16,6 +16,7 @@ bench = false
[features]
default = ["target-x86", "llvm", "editor"]
wasm-cli-run = []
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
@ -67,7 +68,7 @@ libc = "0.2"
libloading = "0.6"
inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.10"
target-lexicon = "0.12.2"
tempfile = "3.1.0"
[dev-dependencies]

View File

@ -74,10 +74,30 @@ pub fn build_file<'a>(
builtin_defs_map,
)?;
use target_lexicon::Architecture;
let emit_wasm = match target.architecture {
Architecture::X86_64 => false,
Architecture::Aarch64(_) => false,
Architecture::Wasm32 => true,
_ => panic!(
"TODO gracefully handle unsupported architecture: {:?}",
target.architecture
),
};
// TODO wasm host extension should be something else ideally
// .bc does not seem to work because
//
// > Non-Emscripten WebAssembly hasn't implemented __builtin_return_address
//
// and zig does not currently emit `.a` webassembly static libraries
let host_extension = if emit_wasm { "zig" } else { "o" };
let app_extension = if emit_wasm { "bc" } else { "o" };
let path_to_platform = loaded.platform_path.clone();
let app_o_file = Builder::new()
.prefix("roc_app")
.suffix(".o")
.suffix(&format!(".{}", app_extension))
.tempfile()
.map_err(|err| {
todo!("TODO Gracefully handle tempfile creation error {:?}", err);
@ -131,7 +151,7 @@ pub fn build_file<'a>(
arena,
loaded,
&roc_file_path,
Triple::host(),
target,
app_o_file,
opt_level,
emit_debug_info,
@ -173,12 +193,13 @@ pub fn build_file<'a>(
let mut host_input_path = PathBuf::from(cwd);
host_input_path.push(&*path_to_platform);
host_input_path.push("host.o");
host_input_path.push("host");
host_input_path.set_extension(host_extension);
// TODO we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.
let rebuild_host_start = SystemTime::now();
rebuild_host(host_input_path.as_path());
rebuild_host(target, host_input_path.as_path());
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
if emit_debug_info {

View File

@ -15,7 +15,8 @@ use std::io;
use std::path::{Path, PathBuf};
use std::process;
use std::process::Command;
use target_lexicon::Triple;
use target_lexicon::BinaryFormat;
use target_lexicon::{Architecture, Triple};
pub mod build;
pub mod repl;
@ -29,7 +30,9 @@ pub const CMD_DOCS: &str = "docs";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend";
pub const ROC_FILE: &str = "ROC_FILE";
pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
@ -50,6 +53,15 @@ pub fn build_app<'a>() -> App<'a> {
.help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
.long(FLAG_BACKEND)
.help("Choose a different backend")
// .requires(BACKEND)
.default_value("llvm")
.possible_values(&["llvm", "wasm", "asm"])
.required(false),
)
.arg(
Arg::with_name(FLAG_LIB)
.long(FLAG_LIB)
@ -118,6 +130,15 @@ pub fn build_app<'a>() -> App<'a> {
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
.long(FLAG_BACKEND)
.help("Choose a different backend")
// .requires(BACKEND)
.default_value("llvm")
.possible_values(&["llvm", "wasm", "asm"])
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to build and run")
@ -159,11 +180,25 @@ pub enum BuildConfig {
BuildAndRun { roc_file_arg_index: usize },
}
fn wasm32_target_tripple() -> Triple {
let mut triple = Triple::unknown();
triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm;
triple
}
#[cfg(feature = "llvm")]
pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use build::build_file;
use BuildConfig::*;
let target = match matches.value_of(FLAG_BACKEND) {
Some("wasm") => wasm32_target_tripple(),
_ => Triple::host(),
};
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
@ -205,7 +240,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
let src_dir = path.parent().unwrap().canonicalize().unwrap();
let res_binary_path = build_file(
&arena,
target,
&target,
src_dir,
path,
opt_level,
@ -240,7 +275,14 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
Ok(outcome.status_code())
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = Command::new(binary_path);
let mut cmd = match target.architecture {
Architecture::Wasm32 => Command::new("wasmtime"),
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = target.architecture {
cmd.arg(binary_path);
}
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:

View File

@ -5,7 +5,6 @@ use roc_cli::{
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
#[cfg(feature = "llvm")]
use roc_cli::build;
@ -25,11 +24,7 @@ fn main() -> io::Result<()> {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build(
&Triple::host(),
&matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
)
build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index })
}
None => {
@ -40,7 +35,6 @@ fn main() -> io::Result<()> {
}
}
Some(CMD_BUILD) => Ok(build(
&Triple::host(),
matches.subcommand_matches(CMD_BUILD).unwrap(),
BuildConfig::BuildOnly,
)?),

View File

@ -222,7 +222,8 @@ fn jit_to_ast_help<'a>(
let tags_map: roc_collections::all::MutMap<_, _> =
tags_vec.iter().cloned().collect();
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
let union_variant =
union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes);
let size = layout.stack_size(env.ptr_bytes);
use roc_mono::layout::WrappedVariant::*;
@ -886,7 +887,8 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
.map(|(a, b)| (a.clone(), b.to_vec()))
.collect();
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
let union_variant =
union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes);
match union_variant {
UnionVariant::ByteUnion(tagnames) => {

View File

@ -132,7 +132,7 @@ pub fn gen_and_eval<'a>(
let builder = context.create_builder();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&context, "",
&context, "", ptr_bytes,
));
// mark our zig-defined builtins as internal

View File

@ -108,6 +108,39 @@ mod cli_run {
assert!(out.status.success());
}
#[cfg(feature = "wasm-cli-run")]
fn check_wasm_output_with_stdin(
file: &Path,
stdin: &[&str],
executable_filename: &str,
flags: &[&str],
expected_ending: &str,
) {
let mut flags = flags.to_vec();
flags.push("--backend=wasm");
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat());
if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
let out = run_cmd(
"wasmtime",
stdin,
&[file.with_file_name(executable_filename).to_str().unwrap()],
);
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, out
);
}
assert!(out.status.success());
}
/// This macro does two things.
///
/// First, it generates and runs a separate test for each of the given
@ -223,13 +256,13 @@ mod cli_run {
// expected_ending: "",
// use_valgrind: true,
// },
// cli:"cli" => Example {
// filename: "Echo.roc",
// executable_filename: "echo",
// stdin: &["Giovanni\n", "Giorgio\n"],
// expected_ending: "Giovanni Giorgio!\n",
// use_valgrind: true,
// },
cli:"cli" => Example {
filename: "Echo.roc",
executable_filename: "echo",
stdin: &["Giovanni\n", "Giorgio\n"],
expected_ending: "Hi, Giovanni Giorgio!\n",
use_valgrind: true,
},
// custom_malloc:"custom-malloc" => Example {
// filename: "Main.roc",
// executable_filename: "custom-malloc-example",
@ -255,9 +288,9 @@ mod cli_run {
let benchmark = $benchmark;
let file_name = examples_dir("benchmarks").join(benchmark.filename);
// TODO fix QuicksortApp and RBTreeCk and then remove this!
// TODO fix QuicksortApp and then remove this!
match benchmark.filename {
"QuicksortApp.roc" | "RBTreeCk.roc" => {
"QuicksortApp.roc" => {
eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename);
return;
}
@ -283,8 +316,48 @@ mod cli_run {
benchmark.use_valgrind,
);
}
)*
#[cfg(feature = "wasm-cli-run")]
mod wasm {
use super::*;
$(
#[test]
#[cfg_attr(not(debug_assertions), serial(benchmark))]
fn $test_name() {
let benchmark = $benchmark;
let file_name = examples_dir("benchmarks").join(benchmark.filename);
// TODO fix QuicksortApp and then remove this!
match benchmark.filename {
"QuicksortApp.roc" | "TestBase64.roc" => {
eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename);
return;
}
_ => {}
}
// Check with and without optimizations
check_wasm_output_with_stdin(
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&[],
benchmark.expected_ending,
);
check_wasm_output_with_stdin(
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--optimize"],
benchmark.expected_ending,
);
}
)*
}
#[test]
#[cfg(not(debug_assertions))]
fn all_benchmarks_have_tests() {
@ -326,8 +399,8 @@ mod cli_run {
rbtree_ck => Example {
filename: "RBTreeCk.roc",
executable_filename: "rbtree-ck",
stdin: &[],
expected_ending: "Node Black 0 {} Empty Empty\n",
stdin: &["100"],
expected_ending: "10\n",
use_valgrind: true,
},
rbtree_insert => Example {

View File

@ -21,6 +21,7 @@ roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_reporting = { path = "../reporting" }
roc_std = { path = "../../roc_std" }
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"] }
@ -29,7 +30,7 @@ libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.10"
target-lexicon = "0.12.2"
[dev-dependencies]
pretty_assertions = "0.5.1"
@ -39,7 +40,7 @@ quickcheck = "0.8"
quickcheck_macros = "0.8"
[features]
default = ["llvm"]
default = ["llvm", "target-webassembly"]
target-arm = []
target-aarch64 = []
target-webassembly = []

View File

@ -26,6 +26,10 @@ pub fn link(
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
match target {
Triple {
architecture: Architecture::Wasm32,
..
} => link_wasm32(target, output_path, input_paths, link_type),
Triple {
operating_system: OperatingSystem::Linux,
..
@ -56,7 +60,7 @@ fn find_zig_str_path() -> PathBuf {
}
#[cfg(not(target_os = "macos"))]
pub fn build_zig_host(
pub fn build_zig_host_native(
env_path: &str,
env_home: &str,
emit_bin: &str,
@ -86,7 +90,7 @@ pub fn build_zig_host(
}
#[cfg(target_os = "macos")]
pub fn build_zig_host(
pub fn build_zig_host_native(
env_path: &str,
env_home: &str,
emit_bin: &str,
@ -158,21 +162,62 @@ pub fn build_zig_host(
.unwrap()
}
pub fn rebuild_host(host_input_path: &Path) {
pub fn build_zig_host_wasm32(
env_path: &str,
env_home: &str,
emit_bin: &str,
zig_host_src: &str,
zig_str_path: &str,
) -> Output {
// NOTE currently just to get compiler warnings if the host code is invalid.
// the produced artifact is not used
//
// NOTE we're emitting LLVM IR here (again, it is not actually used)
//
// we'd like to compile with `-target wasm32-wasi` but that is blocked on
//
// https://github.com/ziglang/zig/issues/9414
Command::new("zig")
.env_clear()
.env("PATH", env_path)
.env("HOME", env_home)
.args(&[
"build-obj",
zig_host_src,
emit_bin,
"--pkg-begin",
"str",
zig_str_path,
"--pkg-end",
// include the zig runtime
// "-fcompiler-rt",
// include libc
"--library",
"c",
"-target",
"i386-linux-musl",
// "wasm32-wasi",
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
])
.output()
.unwrap()
}
pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o");
let zig_host_src = host_input_path.with_file_name("host.zig");
let rust_host_src = host_input_path.with_file_name("host.rs");
let rust_host_dest = host_input_path.with_file_name("rust_host.o");
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
let host_dest = host_input_path.with_file_name("host.o");
let host_dest_native = host_input_path.with_file_name("host.o");
let host_dest_wasm = host_input_path.with_file_name("host.bc");
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
let env_home = env::var("HOME").unwrap_or_else(|_| "".to_string());
if zig_host_src.exists() {
// Compile host.zig
let emit_bin = format!("-femit-bin={}", host_dest.to_str().unwrap());
let zig_str_path = find_zig_str_path();
@ -182,17 +227,31 @@ pub fn rebuild_host(host_input_path: &Path) {
&zig_str_path
);
validate_output(
"host.zig",
"zig",
build_zig_host(
&env_path,
&env_home,
&emit_bin,
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
),
);
let output = match target.architecture {
Architecture::Wasm32 => {
let emit_bin = format!("-femit-llvm-ir={}", host_dest_wasm.to_str().unwrap());
build_zig_host_wasm32(
&env_path,
&env_home,
&emit_bin,
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
)
}
Architecture::X86_64 => {
let emit_bin = format!("-femit-bin={}", host_dest_native.to_str().unwrap());
build_zig_host_native(
&env_path,
&env_home,
&emit_bin,
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
)
}
_ => panic!("Unsupported architecture {:?}", target.architecture),
};
validate_output("host.zig", "zig", output)
} else {
// Compile host.c
let output = Command::new("clang")
@ -233,7 +292,7 @@ pub fn rebuild_host(host_input_path: &Path) {
c_host_dest.to_str().unwrap(),
"-lhost",
"-o",
host_dest.to_str().unwrap(),
host_dest_native.to_str().unwrap(),
])
.output()
.unwrap();
@ -260,7 +319,7 @@ pub fn rebuild_host(host_input_path: &Path) {
c_host_dest.to_str().unwrap(),
rust_host_dest.to_str().unwrap(),
"-o",
host_dest.to_str().unwrap(),
host_dest_native.to_str().unwrap(),
])
.output()
.unwrap();
@ -283,7 +342,7 @@ pub fn rebuild_host(host_input_path: &Path) {
// Clean up c_host.o
let output = Command::new("mv")
.env_clear()
.args(&[c_host_dest, host_dest])
.args(&[c_host_dest, host_dest_native])
.output()
.unwrap();
@ -496,6 +555,38 @@ fn link_macos(
))
}
fn link_wasm32(
_target: &Triple,
output_path: PathBuf,
input_paths: &[&str],
_link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
let zig_str_path = find_zig_str_path();
let child =
Command::new("/home/folkertdev/Downloads/zig-linux-x86_64-0.9.0-dev.848+d5ef5da59/zig")
// .env_clear()
// .env("PATH", &env_path)
.args(&["build-exe"])
.args(input_paths)
.args([
&format!("-femit-bin={}", output_path.to_str().unwrap()),
// include libc
"-lc",
"-target",
"wasm32-wasi",
"--pkg-begin",
"str",
zig_str_path.to_str().unwrap(),
"--pkg-end",
// useful for debugging
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
])
.spawn()?;
Ok((child, output_path))
}
#[cfg(feature = "llvm")]
pub fn module_to_dylib(
module: &inkwell::module::Module,

View File

@ -29,7 +29,7 @@ pub fn gen_from_mono_module(
arena: &bumpalo::Bump,
mut loaded: MonomorphizedModule,
roc_file_path: &Path,
target: target_lexicon::Triple,
target: &target_lexicon::Triple,
app_o_file: &Path,
opt_level: OptLevel,
emit_debug_info: bool,
@ -93,8 +93,9 @@ pub fn gen_from_mono_module(
}
// Generate the binary
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let context = Context::create();
let module = arena.alloc(module_from_builtins(&context, "app"));
let module = arena.alloc(module_from_builtins(&context, "app", ptr_bytes));
// strip Zig debug stuff
// module.strip_debug_info();
@ -134,7 +135,6 @@ pub fn gen_from_mono_module(
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
// Compile and add all the Procs before adding main
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let env = roc_gen_llvm::llvm::build::Env {
arena,
builder: &builder,
@ -219,47 +219,79 @@ pub fn gen_from_mono_module(
}
}
// assemble the .ll into a .bc
let _ = Command::new("llvm-as")
.args(&[
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_bc_file.to_str().unwrap(),
])
.output()
.unwrap();
use target_lexicon::Architecture;
match target.architecture {
Architecture::X86_64 | Architecture::Aarch64(_) => {
// assemble the .ll into a .bc
let _ = Command::new("llvm-as")
.args(&[
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_bc_file.to_str().unwrap(),
])
.output()
.unwrap();
let llc_args = &[
"-filetype=obj",
app_bc_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
];
let llc_args = &[
"-filetype=obj",
app_bc_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
];
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let _: Result<std::process::Output, std::io::Error> =
Command::new(format!("llc-{}", LLVM_VERSION))
.args(llc_args)
.output()
.or_else(|_| Command::new("llc").args(llc_args).output())
.map_err(|_| {
panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION);
});
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let _: Result<std::process::Output, std::io::Error> =
Command::new(format!("llc-{}", LLVM_VERSION))
.args(llc_args)
.output()
.or_else(|_| Command::new("llc").args(llc_args).output())
.map_err(|_| {
panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION);
});
}
Architecture::Wasm32 => {
// assemble the .ll into a .bc
let _ = Command::new("llvm-as")
.args(&[
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
}
_ => unreachable!(),
}
} else {
// Emit the .o file
use target_lexicon::Architecture;
match target.architecture {
Architecture::X86_64 | Architecture::Aarch64(_) => {
let reloc = RelocMode::Default;
let model = CodeModel::Default;
let target_machine =
target::target_machine(target, convert_opt_level(opt_level), reloc, model)
.unwrap();
let reloc = RelocMode::Default;
let model = CodeModel::Default;
let target_machine =
target::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap();
target_machine
.write_to_file(env.module, FileType::Object, app_o_file)
.expect("Writing .o file failed");
target_machine
.write_to_file(env.module, FileType::Object, app_o_file)
.expect("Writing .o file failed");
}
Architecture::Wasm32 => {
// Useful for debugging
// module.print_to_file(app_ll_file);
module.write_bitcode_to_path(app_o_file);
}
_ => panic!(
"TODO gracefully handle unsupported architecture: {:?}",
target.architecture
),
}
}
let emit_o_file = emit_o_file_start.elapsed().unwrap();

View File

@ -17,6 +17,10 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
operating_system: OperatingSystem::Linux,
..
} => "x86_64-unknown-linux-gnu",
Triple {
architecture: Architecture::Wasm32,
..
} => "wasm32-unknown-unknown",
Triple {
architecture: Architecture::Aarch64(_),
operating_system: OperatingSystem::Linux,

View File

@ -20,7 +20,7 @@ pub fn build(b: *Builder) void {
test_step.dependOn(&main_tests.step);
// LLVM IR
const obj_name = "builtins";
const obj_name = "builtins-64bit";
const llvm_obj = b.addObject(obj_name, main_path);
llvm_obj.setBuildMode(mode);
llvm_obj.linkSystemLibrary("c");
@ -30,6 +30,25 @@ pub fn build(b: *Builder) void {
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step);
// LLVM IR 32-bit (wasm)
var target = b.standardTargetOptions(.{});
target.os_tag = std.Target.Os.Tag.linux;
target.cpu_arch = std.Target.Cpu.Arch.i386;
// target.abi = std.Target.Abi.none;
target.abi = std.Target.Abi.musl;
const obj_name_32bit = "builtins-32bit";
const llvm_obj_32bit = b.addObject(obj_name_32bit, main_path);
llvm_obj_32bit.setBuildMode(mode);
llvm_obj_32bit.linkSystemLibrary("c");
llvm_obj_32bit.strip = true;
llvm_obj_32bit.emit_llvm_ir = true;
llvm_obj_32bit.emit_bin = false;
llvm_obj_32bit.target = target;
const ir32bit = b.step("ir-32bit", "Build LLVM ir for 32-bit targets (wasm)");
ir32bit.dependOn(&llvm_obj_32bit.step);
// Object File
// TODO: figure out how to get this to emit symbols that are only scoped to linkage (global but hidden).
// Also, zig has -ffunction-sections, but I am not sure how to add it here.

View File

@ -63,27 +63,21 @@ fn capacityOfLevel(input: usize) usize {
// alignment of the key and value. The tag furthermore indicates
// which has the biggest aligmnent. If both are the same, we put
// the key first
const Alignment = enum(u8) {
Align16KeyFirst,
Align16ValueFirst,
Align8KeyFirst,
Align8ValueFirst,
const Alignment = extern struct {
bits: u8,
const VALUE_BEFORE_KEY_FLAG = 0b1000_0000;
fn toU32(self: Alignment) u32 {
switch (self) {
.Align16KeyFirst => return 16,
.Align16ValueFirst => return 16,
.Align8KeyFirst => return 8,
.Align8ValueFirst => return 8,
}
// xor to wipe the leftmost bit
return self.bits ^ Alignment.VALUE_BEFORE_KEY_FLAG;
}
fn keyFirst(self: Alignment) bool {
switch (self) {
.Align16KeyFirst => return true,
.Align16ValueFirst => return false,
.Align8KeyFirst => return true,
.Align8ValueFirst => return false,
if (self.bits & Alignment.VALUE_BEFORE_KEY_FLAG > 0) {
return false;
} else {
return true;
}
}
};
@ -386,8 +380,8 @@ pub const RocDict = extern struct {
};
// Dict.empty
pub fn dictEmpty() callconv(.C) RocDict {
return RocDict.empty();
pub fn dictEmpty(dict: *RocDict) callconv(.C) void {
dict.* = RocDict.empty();
}
pub fn slotSize(key_size: usize, value_size: usize) usize {

View File

@ -313,6 +313,7 @@ pub const RocStr = extern struct {
}
pub fn asU8ptr(self: RocStr) [*]u8 {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
// return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
@ -429,18 +430,14 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
// Str.fromFloat
pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
// NOTE the compiled zig for float formatting seems to use LLVM11-specific features
// hopefully we can use zig instead of snprintf in the future when we upgrade
const c = @cImport({
// See https://github.com/ziglang/zig/issues/515
@cDefine("_NO_CRT_STDIO_INLINE", "1");
@cInclude("stdio.h");
});
return @call(.{ .modifier = always_inline }, strFromFloatHelp, .{ f64, float });
}
fn strFromFloatHelp(comptime T: type, float: T) RocStr {
var buf: [100]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "{d}", .{float}) catch unreachable;
const result = c.snprintf(&buf, 100, "%f", float);
return RocStr.init(&buf, @intCast(usize, result));
return RocStr.init(&buf, result.len);
}
// Str.split

View File

@ -115,7 +115,7 @@ pub fn decref(
var bytes = bytes_or_null orelse return;
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(8, bytes));
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@sizeOf(isize), bytes));
const refcount = (isizes - 1)[0];
const refcount_isize = @bitCast(isize, refcount);
@ -128,6 +128,20 @@ pub fn decref(
(isizes - 1)[0] = refcount - 1;
}
},
8 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(bytes - 8, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
4 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(bytes - 4, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
else => {
// NOTE enums can currently have an alignment of < 8
if (refcount == REFCOUNT_ONE_ISIZE) {

View File

@ -31,32 +31,59 @@ fn main() {
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_path = bitcode_path.join("builtins-64bit.o");
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
let dest_ir_path = bitcode_path.join("builtins.ll");
let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path");
let dest_ir_path = bitcode_path.join("builtins-32bit.ll");
let dest_ir_32bit = dest_ir_path.to_str().expect("Invalid dest ir path");
let dest_ir_path = bitcode_path.join("builtins-64bit.ll");
let dest_ir_64bit = 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);
println!(
"Compiling zig object & ir to: {} and {}",
src_obj, dest_ir_64bit
);
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);
println!("Compiling 64-bit ir to: {}", dest_ir_64bit);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
println!("Compiling 32-bit ir to: {}", dest_ir_32bit);
run_command(
&bitcode_path,
"zig",
&["build", "ir-32bit", "-Drelease=true"],
);
}
println!("Moving zig object to: {}", dest_obj);
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
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);
let dest_bc_path = bitcode_path.join("builtins-32bit.bc");
let dest_bc_32bit = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 32-bit bitcode to: {}", dest_bc_32bit);
run_command(build_script_dir_path, "llvm-as", &[dest_ir, "-o", dest_bc]);
run_command(
&build_script_dir_path,
"llvm-as",
&[dest_ir_32bit, "-o", dest_bc_32bit],
);
let dest_bc_path = bitcode_path.join("builtins-64bit.bc");
let dest_bc_64bit = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
run_command(
&build_script_dir_path,
"llvm-as",
&[dest_ir_64bit, "-o", dest_bc_64bit],
);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path;

View File

@ -287,7 +287,7 @@ fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = Int(int_var, int_precision_var, i64::MAX.into());
let body = int(int_var, int_precision_var, i64::MAX.into());
Def {
annotation: None,
@ -302,7 +302,7 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = Int(int_var, int_precision_var, i64::MIN.into());
let body = int(int_var, int_precision_var, i64::MIN.into());
Def {
annotation: None,
@ -687,7 +687,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::Eq,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(unbound_zero_var, 0)),
(arg_var, num(unbound_zero_var, 0)),
],
ret_var: bool_var,
};
@ -710,7 +710,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::NumGt,
args: vec![
(arg_var, Num(unbound_zero_var, 0)),
(arg_var, num(unbound_zero_var, 0)),
(arg_var, Var(Symbol::ARG_1)),
],
ret_var: bool_var,
@ -735,7 +735,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(unbound_zero_var, 0)),
(arg_var, num(unbound_zero_var, 0)),
],
ret_var: bool_var,
};
@ -758,14 +758,14 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::Eq,
args: vec![
(arg_var, Int(var_store.fresh(), var_store.fresh(), 1)),
(arg_var, int(var_store.fresh(), var_store.fresh(), 1)),
(
arg_var,
RunLowLevel {
op: LowLevel::NumRemUnchecked,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(unbound_two_var, 2)),
(arg_var, num(unbound_two_var, 2)),
],
ret_var: arg_var,
},
@ -792,14 +792,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::Eq,
args: vec![
(arg_var, Num(arg_num_var, 0)),
(arg_var, num(arg_num_var, 0)),
(
arg_var,
RunLowLevel {
op: LowLevel::NumRemUnchecked,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(arg_num_var, 2)),
(arg_var, num(arg_num_var, 2)),
],
ret_var: arg_var,
},
@ -853,7 +853,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGte,
args: vec![
(float_var, Var(Symbol::ARG_1)),
(float_var, Float(unbound_zero_var, precision_var, 0.0)),
(float_var, float(unbound_zero_var, precision_var, 0.0)),
],
ret_var: bool_var,
}),
@ -899,7 +899,7 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt,
args: vec![
(float_var, Var(Symbol::ARG_1)),
(float_var, Float(unbound_zero_var, precision_var, 0.0)),
(float_var, float(unbound_zero_var, precision_var, 0.0)),
],
ret_var: bool_var,
}),
@ -1139,7 +1139,7 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = Int(int_var, int_precision_var, i128::MAX);
let body = int(int_var, int_precision_var, i128::MAX);
let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap();
@ -1172,7 +1172,7 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::Eq,
args: vec![
(len_var, Num(unbound_zero_var, 0)),
(len_var, num(unbound_zero_var, 0)),
(
len_var,
RunLowLevel {
@ -2051,7 +2051,7 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![
(list_var, Var(Symbol::ARG_1)),
(closure_var, list_sum_add(num_var, var_store)),
(num_var, Num(var_store.fresh(), 0)),
(num_var, num(var_store.fresh(), 0)),
],
ret_var,
};
@ -2093,7 +2093,7 @@ fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![
(list_var, Var(Symbol::ARG_1)),
(closure_var, list_product_mul(num_var, var_store)),
(num_var, Num(var_store.fresh(), 1)),
(num_var, num(var_store.fresh(), 1)),
],
ret_var,
};
@ -2571,7 +2571,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq,
args: vec![
(num_var, Var(Symbol::ARG_2)),
(num_var, Num(unbound_zero_var, 0)),
(num_var, num(unbound_zero_var, 0)),
],
ret_var: bool_var,
},
@ -2674,7 +2674,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq,
args: vec![
(num_var, Var(Symbol::ARG_2)),
(num_var, Float(unbound_zero_var, precision_var, 0.0)),
(num_var, float(unbound_zero_var, precision_var, 0.0)),
],
ret_var: bool_var,
},
@ -2739,7 +2739,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)),
(
num_var,
Int(unbound_zero_var, unbound_zero_precision_var, 0),
int(unbound_zero_var, unbound_zero_precision_var, 0),
),
],
ret_var: bool_var,
@ -2792,9 +2792,9 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let list_var = var_store.fresh();
let len_var = var_store.fresh();
let zero_var = var_store.fresh();
let zero_precision_var = var_store.fresh();
let len_var = Variable::NAT;
let zero_var = len_var;
let zero_precision_var = Variable::NATURAL;
let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh();
@ -2809,7 +2809,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, Int(zero_var, zero_precision_var, 0)),
(len_var, int(zero_var, zero_precision_var, 0)),
(
len_var,
RunLowLevel {
@ -2833,7 +2833,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(len_var, Int(zero_var, zero_precision_var, 0)),
(len_var, int(zero_var, zero_precision_var, 0)),
],
ret_var: list_elem_var,
},
@ -2890,7 +2890,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, Int(num_var, num_precision_var, 0)),
(len_var, int(num_var, num_precision_var, 0)),
(
len_var,
RunLowLevel {
@ -2929,7 +2929,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var,
},
),
(arg_var, Int(num_var, num_precision_var, 1)),
(arg_var, int(num_var, num_precision_var, 1)),
],
ret_var: len_var,
},
@ -3403,7 +3403,7 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level
add_var,
RunLowLevel {
ret_var: cast_var,
args: vec![(cast_var, Num(var_store.fresh(), offset))],
args: vec![(cast_var, num(var_store.fresh(), offset))],
op: LowLevel::NumIntCast,
},
),
@ -3489,3 +3489,18 @@ fn defn_help(
loc_body: Box::new(no_region(body)),
}
}
#[inline(always)]
fn int(num_var: Variable, precision_var: Variable, i: i128) -> Expr {
Int(num_var, precision_var, i.to_string().into_boxed_str(), i)
}
#[inline(always)]
fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr {
Float(num_var, precision_var, f.to_string().into_boxed_str(), f)
}
#[inline(always)]
fn num(num_var: Variable, i: i64) -> Expr {
Num(num_var, i.to_string().into_boxed_str(), i)
}

View File

@ -742,9 +742,9 @@ fn pattern_to_vars_by_symbol(
}
}
NumLiteral(_, _)
| IntLiteral(_, _)
| FloatLiteral(_, _)
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| MalformedPattern(_, _)

View File

@ -21,7 +21,7 @@ use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias;
use std::fmt::Debug;
use std::{char, i64, u32};
use std::{char, u32};
#[derive(Clone, Default, Debug, PartialEq)]
pub struct Output {
@ -52,11 +52,11 @@ pub enum Expr {
// Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages
Num(Variable, i64),
Num(Variable, Box<str>, i64),
// Int and Float store a variable to generate better error messages
Int(Variable, Variable, i128),
Float(Variable, Variable, f64),
Int(Variable, Variable, Box<str>, i128),
Float(Variable, Variable, Box<str>, f64),
Str(Box<str>),
List {
elem_var: Variable,
@ -206,14 +206,23 @@ pub fn canonicalize_expr<'a>(
use Expr::*;
let (expr, output) = match expr {
ast::Expr::Num(string) => {
let answer = num_expr_from_result(var_store, finish_parsing_int(*string), region, env);
ast::Expr::Num(str) => {
let answer = num_expr_from_result(
var_store,
finish_parsing_int(*str).map(|int| (*str, int)),
region,
env,
);
(answer, Output::default())
}
ast::Expr::Float(string) => {
let answer =
float_expr_from_result(var_store, finish_parsing_float(string), region, env);
ast::Expr::Float(str) => {
let answer = float_expr_from_result(
var_store,
finish_parsing_float(str).map(|f| (*str, f)),
region,
env,
);
(answer, Output::default())
}
@ -795,8 +804,16 @@ pub fn canonicalize_expr<'a>(
is_negative,
} => {
// the minus sign is added before parsing, to get correct overflow/underflow behavior
let result = finish_parsing_base(string, *base, *is_negative);
let answer = int_expr_from_result(var_store, result, region, *base, env);
let answer = match finish_parsing_base(string, *base, *is_negative) {
Ok(int) => {
// Done in this kinda round about way with intermediate variables
// to keep borrowed values around and make this compile
let int_string = int.to_string();
let int_str = int_string.as_str();
int_expr_from_result(var_store, Ok((int_str, int as i128)), region, *base, env)
}
Err(e) => int_expr_from_result(var_store, Err(e), region, *base, env),
};
(answer, Output::default())
}
@ -1217,9 +1234,9 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
match expr {
// Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages
other @ Num(_, _)
| other @ Int(_, _, _)
| other @ Float(_, _, _)
other @ Num(_, _, _)
| other @ Int(_, _, _, _)
| other @ Float(_, _, _, _)
| other @ Str { .. }
| other @ RuntimeError(_)
| other @ EmptyRecord

View File

@ -382,9 +382,9 @@ fn fix_values_captured_in_closure_pattern(
}
}
Identifier(_)
| NumLiteral(_, _)
| IntLiteral(_, _)
| FloatLiteral(_, _)
| NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| Shadowed(_, _)
@ -438,9 +438,9 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
}
Num(_, _)
| Int(_, _, _)
| Float(_, _, _)
Num(_, _, _)
| Int(_, _, _, _)
| Float(_, _, _, _)
| Str(_)
| Var(_)
| EmptyRecord

View File

@ -16,12 +16,12 @@ use std::i64;
#[inline(always)]
pub fn num_expr_from_result(
var_store: &mut VarStore,
result: Result<i64, (&str, IntErrorKind)>,
result: Result<(&str, i64), (&str, IntErrorKind)>,
region: Region,
env: &mut Env,
) -> Expr {
match result {
Ok(int) => Expr::Num(var_store.fresh(), int),
Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num),
Err((raw, error)) => {
// (Num *) compiles to Int if it doesn't
// get specialized to something else first,
@ -38,14 +38,14 @@ pub fn num_expr_from_result(
#[inline(always)]
pub fn int_expr_from_result(
var_store: &mut VarStore,
result: Result<i64, (&str, IntErrorKind)>,
result: Result<(&str, i128), (&str, IntErrorKind)>,
region: Region,
base: Base,
env: &mut Env,
) -> Expr {
// Int stores a variable to generate better error messages
match result {
Ok(int) => Expr::Int(var_store.fresh(), var_store.fresh(), int.into()),
Ok((str, int)) => Expr::Int(var_store.fresh(), var_store.fresh(), (*str).into(), int),
Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into());
@ -59,13 +59,13 @@ pub fn int_expr_from_result(
#[inline(always)]
pub fn float_expr_from_result(
var_store: &mut VarStore,
result: Result<f64, (&str, FloatErrorKind)>,
result: Result<(&str, f64), (&str, FloatErrorKind)>,
region: Region,
env: &mut Env,
) -> Expr {
// Float stores a variable to generate better error messages
match result {
Ok(float) => Expr::Float(var_store.fresh(), var_store.fresh(), float),
Ok((str, float)) => Expr::Float(var_store.fresh(), var_store.fresh(), (*str).into(), float),
Err((raw, error)) => {
let runtime_error = InvalidFloat(error, region, raw.into());

View File

@ -25,9 +25,9 @@ pub enum Pattern {
ext_var: Variable,
destructs: Vec<Located<RecordDestruct>>,
},
IntLiteral(Variable, i64),
NumLiteral(Variable, i64),
FloatLiteral(Variable, f64),
IntLiteral(Variable, Box<str>, i64),
NumLiteral(Variable, Box<str>, i64),
FloatLiteral(Variable, Box<str>, f64),
StrLiteral(Box<str>),
Underscore,
@ -85,9 +85,9 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
}
}
NumLiteral(_, _)
| IntLiteral(_, _)
| FloatLiteral(_, _)
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| MalformedPattern(_, _)
@ -185,13 +185,13 @@ pub fn canonicalize_pattern<'a>(
}
}
FloatLiteral(string) => match pattern_type {
WhenBranch => match finish_parsing_float(string) {
FloatLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_float(str) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region)
}
Ok(float) => Pattern::FloatLiteral(var_store.fresh(), float),
Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (*str).into(), float),
},
ptype => unsupported_pattern(env, ptype, region),
},
@ -201,13 +201,13 @@ pub fn canonicalize_pattern<'a>(
TopLevelDef | DefExpr => bad_underscore(env, region),
},
NumLiteral(string) => match pattern_type {
WhenBranch => match finish_parsing_int(string) {
NumLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_int(str) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
}
Ok(int) => Pattern::NumLiteral(var_store.fresh(), int),
Ok(int) => Pattern::NumLiteral(var_store.fresh(), (*str).into(), int),
},
ptype => unsupported_pattern(env, ptype, region),
},
@ -223,11 +223,10 @@ pub fn canonicalize_pattern<'a>(
malformed_pattern(env, problem, region)
}
Ok(int) => {
if *is_negative {
Pattern::IntLiteral(var_store.fresh(), -int)
} else {
Pattern::IntLiteral(var_store.fresh(), int)
}
let sign_str = if *is_negative { "-" } else { "" };
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
let i = if *is_negative { -int } else { int };
Pattern::IntLiteral(var_store.fresh(), int_str, i)
}
},
ptype => unsupported_pattern(env, ptype, region),
@ -473,9 +472,9 @@ fn add_bindings_from_patterns(
answer.push((*symbol, *region));
}
}
NumLiteral(_, _)
| IntLiteral(_, _)
| FloatLiteral(_, _)
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| Shadowed(_, _)

View File

@ -32,7 +32,7 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value {
Expr::Float(_, _, actual) => {
Expr::Float(_, _, _, actual) => {
assert_eq!(expected, actual);
}
actual => {
@ -46,7 +46,7 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value {
Expr::Int(_, _, actual) => {
Expr::Int(_, _, _, actual) => {
assert_eq!(expected, actual);
}
actual => {
@ -60,7 +60,7 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value {
Expr::Num(_, actual) => {
Expr::Num(_, _, actual) => {
assert_eq!(expected, actual);
}
actual => {

View File

@ -96,8 +96,8 @@ pub fn constrain_expr(
expected: Expected<Type>,
) -> Constraint {
match expr {
Int(var, precision, _) => int_literal(*var, *precision, expected, region),
Num(var, _) => exists(
Int(var, precision, _, _) => int_literal(*var, *precision, expected, region),
Num(var, _, _) => exists(
vec![*var],
Eq(
crate::builtins::num_num(Type::Variable(*var)),
@ -106,7 +106,7 @@ pub fn constrain_expr(
region,
),
),
Float(var, precision, _) => float_literal(*var, *precision, expected, region),
Float(var, precision, _, _) => float_literal(*var, *precision, expected, region),
EmptyRecord => constrain_empty_record(region, expected),
Expr::Record { record_var, fields } => {
if fields.is_empty() {

View File

@ -56,9 +56,9 @@ fn headers_from_annotation_help(
| Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| NumLiteral(_, _)
| IntLiteral(_, _)
| FloatLiteral(_, _)
| NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -143,7 +143,7 @@ pub fn constrain_pattern(
);
}
NumLiteral(var, _) => {
NumLiteral(var, _, _) => {
state.vars.push(*var);
state.constraints.push(Constraint::Pattern(
@ -154,7 +154,7 @@ pub fn constrain_pattern(
));
}
IntLiteral(precision_var, _) => {
IntLiteral(precision_var, _, _) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Int,
@ -163,7 +163,7 @@ pub fn constrain_pattern(
));
}
FloatLiteral(precision_var, _) => {
FloatLiteral(precision_var, _, _) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,

View File

@ -21,7 +21,7 @@ roc_mono = { path = "../mono" }
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"] }
target-lexicon = "0.10"
target-lexicon = "0.12.2"
libloading = "0.6"
object = { version = "0.24", features = ["write"] }

View File

@ -228,6 +228,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
fn load_args<'a>(
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)],
_ret_layout: &Layout<'a>,
) -> Result<(), String> {
Err("Loading args not yet implemented for AArch64".to_string())
}
@ -242,6 +243,20 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
) -> Result<u32, String> {
Err("Storing args not yet implemented for AArch64".to_string())
}
fn return_struct<'a>(
_buf: &mut Vec<'a, u8>,
_struct_offset: i32,
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_ret_reg: Option<AArch64GeneralReg>,
) -> Result<(), String> {
Err("Returning structs not yet implemented for AArch64".to_string())
}
fn returns_via_arg_pointer(_ret_layout: &Layout) -> Result<bool, String> {
Err("Returning via arg pointer not yet implemented for AArch64".to_string())
}
}
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {

View File

@ -10,6 +10,8 @@ use target_lexicon::Triple;
pub mod aarch64;
pub mod x86_64;
const PTR_SIZE: u32 = 64;
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
const GENERAL_PARAM_REGS: &'static [GeneralReg];
const GENERAL_RETURN_REGS: &'static [GeneralReg];
@ -49,6 +51,8 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>,
) -> Result<(), String>;
// store_args stores the args in registers and on the stack for function calling.
@ -61,6 +65,19 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>,
) -> Result<u32, String>;
// return_struct returns a struct currently on the stack at `struct_offset`.
// It does so using registers and stack as necessary.
fn return_struct<'a>(
buf: &mut Vec<'a, u8>,
struct_offset: i32,
struct_size: u32,
field_layouts: &[Layout<'a>],
ret_reg: Option<GeneralReg>,
) -> Result<(), String>;
// returns true if the layout should be returned via an argument pointer.
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String>;
}
/// Assembler contains calls to the backend assembly generator.
@ -160,8 +177,6 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
#[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)]
pub enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
// These may need layout, but I am not sure.
// I think whenever a symbol would be used, we specify layout anyways.
GeneralReg(GeneralReg),
FloatReg(FloatReg),
Base(i32),
@ -186,7 +201,7 @@ pub struct Backend64Bit<
last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
symbols_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
literal_map: MutMap<Symbol, Literal<'a>>,
// This should probably be smarter than a vec.
@ -226,7 +241,7 @@ impl<
relocs: bumpalo::vec!(in env.arena),
last_seen_map: MutMap::default(),
free_map: MutMap::default(),
symbols_map: MutMap::default(),
symbol_storage_map: MutMap::default(),
literal_map: MutMap::default(),
general_free_regs: bumpalo::vec![in env.arena],
general_used_regs: bumpalo::vec![in env.arena],
@ -248,7 +263,7 @@ impl<
self.fn_call_stack_size = 0;
self.last_seen_map.clear();
self.free_map.clear();
self.symbols_map.clear();
self.symbol_storage_map.clear();
self.buf.clear();
self.general_used_callee_saved_regs.clear();
self.general_free_regs.clear();
@ -324,10 +339,14 @@ impl<
Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
}
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String> {
CC::load_args(&mut self.symbols_map, args)?;
fn load_args(
&mut self,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
CC::load_args(&mut self.symbol_storage_map, args, ret_layout)?;
// Update used and free regs.
for (sym, storage) in &self.symbols_map {
for (sym, storage) in &self.symbol_storage_map {
match storage {
SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => {
self.general_free_regs.retain(|r| *r != *reg);
@ -386,7 +405,7 @@ impl<
// Put values in param regs or on top of the stack.
let tmp_stack_size = CC::store_args(
&mut self.buf,
&self.symbols_map,
&self.symbol_storage_map,
args,
arg_layouts,
ret_layout,
@ -421,7 +440,7 @@ impl<
_cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations.
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
_ret_layout: &Layout<'a>,
ret_layout: &Layout<'a>,
) -> Result<(), String> {
// Switches are a little complex due to keeping track of jumps.
// In general I am trying to not have to loop over things multiple times or waste memory.
@ -439,7 +458,7 @@ impl<
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Build all statements in this branch.
self.build_stmt(stmt)?;
self.build_stmt(stmt, ret_layout)?;
// Build unconditional jump to the end of this switch.
// Since we don't know the offset yet, set it to 0 and overwrite later.
@ -463,7 +482,7 @@ impl<
}
let (branch_info, stmt) = default_branch;
if let BranchInfo::None = branch_info {
self.build_stmt(stmt)?;
self.build_stmt(stmt, ret_layout)?;
// Update all return jumps to jump past the default case.
let ret_offset = self.buf.len();
@ -560,6 +579,61 @@ impl<
Ok(())
}
fn create_struct(
&mut self,
sym: &Symbol,
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String> {
if let Layout::Struct(field_layouts) = layout {
let struct_size = layout.stack_size(PTR_SIZE);
if struct_size > 0 {
let offset = self.increase_stack_size(struct_size)?;
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
let mut current_offset = offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(current_offset, field, field_layout)?;
let field_size = field_layout.stack_size(PTR_SIZE);
current_offset += field_size as i32;
}
} else {
self.symbol_storage_map.insert(*sym, SymbolStorage::Base(0));
}
Ok(())
} else {
// This is a single element struct. Just copy the single field to the stack.
let struct_size = layout.stack_size(PTR_SIZE);
let offset = self.increase_stack_size(struct_size)?;
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
self.copy_symbol_to_stack_offset(offset, &fields[0], layout)?;
Ok(())
}
}
fn load_struct_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
index: u64,
field_layouts: &'a [Layout<'a>],
) -> Result<(), String> {
if let Some(SymbolStorage::Base(struct_offset)) = self.symbol_storage_map.get(structure) {
let mut data_offset = *struct_offset;
for i in 0..index {
let field_size = field_layouts[i as usize].stack_size(PTR_SIZE);
data_offset += field_size as i32;
}
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(data_offset));
Ok(())
} else {
Err(format!("unknown struct: {:?}", structure))
}
}
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> {
match lit {
Literal::Int(x) => {
@ -579,7 +653,7 @@ impl<
}
fn free_symbol(&mut self, sym: &Symbol) {
self.symbols_map.remove(sym);
self.symbol_storage_map.remove(sym);
for i in 0..self.general_used_regs.len() {
let (reg, saved_sym) = self.general_used_regs[i];
if saved_sym == *sym {
@ -590,8 +664,8 @@ impl<
}
}
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> {
let val = self.symbols_map.get(sym);
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> {
let val = self.symbol_storage_map.get(sym);
match val {
Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()),
Some(SymbolStorage::GeneralReg(reg)) => {
@ -605,6 +679,48 @@ impl<
ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg);
Ok(())
}
Some(SymbolStorage::Base(offset)) => match layout {
Layout::Builtin(Builtin::Int64) => {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
Ok(())
}
Layout::Builtin(Builtin::Float64) => {
ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset);
Ok(())
}
Layout::Struct(field_layouts) => {
let struct_size = layout.stack_size(PTR_SIZE);
if struct_size > 0 {
let struct_offset = if let Some(SymbolStorage::Base(offset)) =
self.symbol_storage_map.get(sym)
{
Ok(*offset)
} else {
Err(format!("unknown struct: {:?}", sym))
}?;
let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER)
{
Some(self.load_to_general_reg(&Symbol::RET_POINTER)?)
} else {
None
};
CC::return_struct(
&mut self.buf,
struct_offset,
struct_size,
field_layouts,
ret_reg,
)
} else {
// Nothing to do for empty struct
Ok(())
}
}
x => Err(format!(
"returning symbol with layout, {:?}, is not yet implemented",
x
)),
},
Some(x) => Err(format!(
"returning symbol storage, {:?}, is not yet implemented",
x
@ -624,6 +740,25 @@ impl<
CC: CallConv<GeneralReg, FloatReg>,
> Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC>
{
fn get_tmp_general_reg(&mut self) -> Result<GeneralReg, String> {
if !self.general_free_regs.is_empty() {
let free_reg = *self
.general_free_regs
.get(self.general_free_regs.len() - 1)
.unwrap();
if CC::general_callee_saved(&free_reg) {
self.general_used_callee_saved_regs.insert(free_reg);
}
Ok(free_reg)
} else if !self.general_used_regs.is_empty() {
let (reg, sym) = self.general_used_regs.remove(0);
self.free_to_stack(&sym)?;
Ok(reg)
} else {
Err("completely out of general purpose registers".to_string())
}
}
fn claim_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> {
let reg = if !self.general_free_regs.is_empty() {
let free_reg = self.general_free_regs.pop().unwrap();
@ -640,7 +775,7 @@ impl<
}?;
self.general_used_regs.push((reg, *sym));
self.symbols_map
self.symbol_storage_map
.insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg)
}
@ -661,27 +796,28 @@ impl<
}?;
self.float_used_regs.push((reg, *sym));
self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg));
self.symbol_storage_map
.insert(*sym, SymbolStorage::FloatReg(reg));
Ok(reg)
}
fn load_to_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> {
let val = self.symbols_map.remove(sym);
let val = self.symbol_storage_map.remove(sym);
match val {
Some(SymbolStorage::GeneralReg(reg)) => {
self.symbols_map
self.symbol_storage_map
.insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg)
}
Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_general_reg(sym)?;
self.symbols_map
self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg)
}
Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => {
self.symbols_map
self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
Ok(reg)
}
@ -693,21 +829,22 @@ impl<
}
fn load_to_float_reg(&mut self, sym: &Symbol) -> Result<FloatReg, String> {
let val = self.symbols_map.remove(sym);
let val = self.symbol_storage_map.remove(sym);
match val {
Some(SymbolStorage::FloatReg(reg)) => {
self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg));
self.symbol_storage_map
.insert(*sym, SymbolStorage::FloatReg(reg));
Ok(reg)
}
Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_float_reg(sym)?;
self.symbols_map
self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg)
}
Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => {
self.symbols_map
self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
Ok(reg)
}
@ -719,54 +856,94 @@ impl<
}
fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> {
let val = self.symbols_map.remove(sym);
let val = self.symbol_storage_map.remove(sym);
match val {
Some(SymbolStorage::GeneralReg(reg)) => {
let offset = self.increase_stack_size(8)? as i32;
let offset = self.increase_stack_size(8)?;
// For base addresssing, use the negative offset - 8.
ASM::mov_base32_reg64(&mut self.buf, -offset - 8, reg);
self.symbols_map
.insert(*sym, SymbolStorage::Base(-offset - 8));
ASM::mov_base32_reg64(&mut self.buf, offset, reg);
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
Some(SymbolStorage::FloatReg(reg)) => {
let offset = self.increase_stack_size(8)? as i32;
let offset = self.increase_stack_size(8)?;
// For base addresssing, use the negative offset.
ASM::mov_base32_freg64(&mut self.buf, -offset - 8, reg);
self.symbols_map
.insert(*sym, SymbolStorage::Base(-offset - 8));
ASM::mov_base32_freg64(&mut self.buf, offset, reg);
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
Some(SymbolStorage::Base(offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
Some(SymbolStorage::BaseAndFloatReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
None => Err(format!("Unknown symbol: {}", sym)),
}
}
/// increase_stack_size increase the current stack size and returns the offset of the stack.
fn increase_stack_size(&mut self, amount: u32) -> Result<u32, String> {
/// increase_stack_size increase the current stack size `amount` bytes.
/// It returns base pointer relative offset of the new data.
fn increase_stack_size(&mut self, amount: u32) -> Result<i32, String> {
debug_assert!(amount > 0);
let offset = self.stack_size;
if let Some(new_size) = self.stack_size.checked_add(amount) {
// Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed.
if new_size > i32::MAX as u32 {
Err("Ran out of stack space".to_string())
} else {
self.stack_size = new_size;
let offset = -(self.stack_size as i32);
Ok(offset)
}
} else {
Err("Ran out of stack space".to_string())
}
}
fn copy_symbol_to_stack_offset(
&mut self,
to_offset: i32,
sym: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
match layout {
Layout::Builtin(Builtin::Int64) => {
let reg = self.load_to_general_reg(sym)?;
ASM::mov_base32_reg64(&mut self.buf, to_offset, reg);
Ok(())
}
Layout::Builtin(Builtin::Float64) => {
let reg = self.load_to_float_reg(sym)?;
ASM::mov_base32_freg64(&mut self.buf, to_offset, reg);
Ok(())
}
Layout::Struct(_) if layout.safe_to_memcpy() => {
let tmp_reg = self.get_tmp_general_reg()?;
if let Some(SymbolStorage::Base(from_offset)) = self.symbol_storage_map.get(sym) {
for i in 0..layout.stack_size(PTR_SIZE) as i32 {
ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i);
ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg);
}
Ok(())
} else {
Err(format!("unknown struct: {:?}", sym))
}
}
x => Err(format!(
"copying data to the stack with layout, {:?}, not implemented yet",
x
)),
}
}
}

View File

@ -1,4 +1,4 @@
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage};
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE};
use crate::Relocation;
use bumpalo::collections::Vec;
use roc_collections::all::MutMap;
@ -177,10 +177,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
let mut general_i = 0;
let mut float_i = 0;
if X86_64SystemV::returns_via_arg_pointer(ret_layout)? {
symbol_map.insert(
Symbol::RET_POINTER,
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
);
general_i += 1;
}
for (layout, sym) in args.iter() {
match layout {
Layout::Builtin(Builtin::Int64) => {
@ -359,6 +367,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
}
Ok(stack_offset as u32)
}
fn return_struct<'a>(
_buf: &mut Vec<'a, u8>,
_struct_offset: i32,
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_ret_reg: Option<X86_64GeneralReg>,
) -> Result<(), String> {
Err("Returning structs not yet implemented for X86_64".to_string())
}
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String> {
// TODO: This may need to be more complex/extended to fully support the calling convention.
// details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
Ok(ret_layout.stack_size(PTR_SIZE) > 16)
}
}
impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
@ -477,9 +501,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
for (i, (layout, sym)) in args.iter().enumerate() {
let mut i = 0;
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout)? {
symbol_map.insert(
Symbol::RET_POINTER,
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]),
);
i += 1;
}
for (layout, sym) in args.iter() {
if i < Self::GENERAL_PARAM_REGS.len() {
match layout {
Layout::Builtin(Builtin::Int64) => {
@ -496,6 +529,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
));
}
}
i += 1;
} else {
base_offset += match layout {
Layout::Builtin(Builtin::Int64) => 8,
@ -653,6 +687,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
}
Ok(stack_offset as u32)
}
fn return_struct<'a>(
_buf: &mut Vec<'a, u8>,
_struct_offset: i32,
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_ret_reg: Option<X86_64GeneralReg>,
) -> Result<(), String> {
Err("Returning structs not yet implemented for X86_64WindowsFastCall".to_string())
}
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String> {
// TODO: This is not fully correct there are some exceptions for "vector" types.
// details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values
Ok(ret_layout.stack_size(PTR_SIZE) > 8)
}
}
#[inline(always)]

View File

@ -66,7 +66,11 @@ where
// load_args is used to let the backend know what the args are.
// The backend should track these args so it can use them as needed.
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String>;
fn load_args(
&mut self,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String>;
/// Used for generating wrappers for malloc/realloc/free
fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>;
@ -74,29 +78,29 @@ where
/// build_proc creates a procedure and outputs it to the wrapped object writer.
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset();
self.load_args(proc.args)?;
self.load_args(proc.args, &proc.ret_layout)?;
// let start = std::time::Instant::now();
self.scan_ast(&proc.body);
self.create_free_map();
// let duration = start.elapsed();
// println!("Time to calculate lifetimes: {:?}", duration);
// println!("{:?}", self.last_seen_map());
self.build_stmt(&proc.body)?;
self.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize()
}
/// build_stmt builds a statement and outputs at the end of the buffer.
fn build_stmt(&mut self, stmt: &Stmt<'a>) -> Result<(), String> {
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
match stmt {
Stmt::Let(sym, expr, layout, following) => {
self.build_expr(sym, expr, layout)?;
self.free_symbols(stmt);
self.build_stmt(following)?;
self.build_stmt(following, ret_layout)?;
Ok(())
}
Stmt::Ret(sym) => {
self.load_literal_symbols(&[*sym])?;
self.return_symbol(sym)?;
self.return_symbol(sym, ret_layout)?;
self.free_symbols(stmt);
Ok(())
}
@ -218,6 +222,15 @@ where
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
}
}
Expr::Struct(fields) => {
self.load_literal_symbols(fields)?;
self.create_struct(sym, layout, fields)
}
Expr::StructAtIndex {
index,
field_layouts,
structure,
} => self.load_struct_at_index(sym, structure, *index, field_layouts),
x => Err(format!("the expression, {:?}, is not yet implemented", x)),
}
}
@ -377,11 +390,28 @@ where
Ok(())
}
/// create_struct creates a struct with the elements specified loaded into it as data.
fn create_struct(
&mut self,
sym: &Symbol,
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String>;
/// load_struct_at_index loads into `sym` the value at `index` in `structure`.
fn load_struct_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
index: u64,
field_layouts: &'a [Layout<'a>],
) -> Result<(), String>;
/// load_literal sets a symbol to be equal to a literal.
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>;
/// return_symbol moves a symbol to the correct return location for the backend.
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String>;
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>;
/// free_symbols will free all symbols for the given statement.
fn free_symbols(&mut self, stmt: &Stmt<'a>) {

View File

@ -0,0 +1,937 @@
#[macro_use]
extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_records {
#[test]
fn basic_record() {
assert_evals_to!(
indoc!(
r#"
{ y: 17, x: 15, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn nested_record() {
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
"#
),
12,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
"#
),
2,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn f64_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.x
"#
),
15.1,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.y
"#
),
17.2,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.z
"#
),
19.3,
f64
);
}
// #[test]
// fn fn_record() {
// assert_evals_to!(
// indoc!(
// r#"
// getRec = \x -> { y: 17, x, z: 19 }
// (getRec 15).x
// "#
// ),
// 15,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.y
// "#
// ),
// 17,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z
// "#
// ),
// 19,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z + rec.x
// "#
// ),
// 34,
// i64
// );
// }
#[test]
fn def_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17, x: 15, z: 19 }
rec.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z
"#
),
19,
i64
);
}
#[test]
fn when_on_record() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2 } is
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn when_record_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: var } -> var + 3
"#
),
5,
i64
);
}
#[test]
fn let_with_record_pattern() {
assert_evals_to!(
indoc!(
r#"
{ x } = { x: 0x2, y: 3.14 }
x
"#
),
2,
i64
);
}
#[test]
fn record_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: 0x4 } -> 5
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn twice_record_access() {
assert_evals_to!(
indoc!(
r#"
x = {a: 0x2, b: 0x3 }
x.a + x.b
"#
),
5,
i64
);
}
#[test]
fn empty_record() {
assert_evals_to!(
indoc!(
r#"
v = {}
v
"#
),
(),
()
);
}
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3 }
"#
),
3,
i64
);
}
// #[test]
// fn i64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// (3, 5),
// (i64, i64)
// );
// }
// // #[test]
// // fn i64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3, y: 5, z: 17 }
// // "#
// // ),
// // (3, 5, 17),
// // (i64, i64, i64)
// // );
// // }
// #[test]
// fn f64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3.1, y: 5.1 }
// "#
// ),
// (3.1, 5.1),
// (f64, f64)
// );
// }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// // #[test]
// // fn bool_record4_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
// // record = { a: True, b: True, c : True, d : Bool }
// // record
// // "#
// // ),
// // (true, false, false, true),
// // (bool, bool, bool, bool)
// // );
// // }
// #[test]
// fn i64_record1_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3 }
// "#
// ),
// 3,
// i64
// );
// }
// // #[test]
// // fn i64_record9_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
// // "#
// // ),
// // (3, 5, 17, 1, 9, 12, 13, 14, 15),
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64)
// // );
// // }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// #[test]
// fn bool_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// x : Bool
// x = True
// x
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn optional_field_when_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// main =
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_function_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \{ x ? 10, y } -> x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default_nested() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_singleton_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x : 4 } is
// { x ? 3 } -> x
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn optional_field_empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { } is
// { x ? 3 } -> x
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn return_record_2() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// [3, 5],
// [i64; 2]
// );
// }
// #[test]
// fn return_record_3() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5, z: 4 }
// "#
// ),
// (3, 5, 4),
// (i64, i64, i64)
// );
// }
// #[test]
// fn return_record_4() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2 }
// "#
// ),
// [3, 5, 4, 2],
// [i64; 4]
// );
// }
// #[test]
// fn return_record_5() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1 }
// "#
// ),
// [3, 5, 4, 2, 1],
// [i64; 5]
// );
// }
// #[test]
// fn return_record_6() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
// "#
// ),
// [3, 5, 4, 2, 1, 7],
// [i64; 6]
// );
// }
// #[test]
// fn return_record_7() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
// "#
// ),
// [3, 5, 4, 2, 1, 7, 8],
// [i64; 7]
// );
// }
// #[test]
// fn return_record_float_int() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3.14, b: 0x1 }
// "#
// ),
// (3.14, 0x1),
// (f64, i64)
// );
// }
// #[test]
// fn return_record_int_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 0x1, b: 3.14 }
// "#
// ),
// (0x1, 3.14),
// (i64, f64)
// );
// }
// #[test]
// fn return_record_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14 }
// "#
// ),
// (6.28, 3.14),
// (f64, f64)
// );
// }
// #[test]
// fn return_record_float_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14, c: 0.1 }
// "#
// ),
// (6.28, 3.14, 0.1),
// (f64, f64, f64)
// );
// }
// #[test]
// fn return_nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
// "#
// ),
// (0x0, (6.28, 3.14, 0.1)),
// (i64, (f64, f64, f64))
// );
// }
// #[test]
// fn accessor() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn accessor_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 }
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn update_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42, bar: 6 }
// { rec & foo: rec.foo + 1 }
// "#
// ),
// (6, 43),
// (i64, i64)
// );
// }
#[test]
fn update_single_element_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42}
{ rec & foo: rec.foo + 1 }
"#
),
43,
i64
);
}
// #[test]
// fn booleans_in_record() {
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
// (true, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
// (false, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
// (true, false),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
// (false, false),
// (bool, bool)
// );
// }
// #[test]
// fn alignment_in_record() {
// assert_evals_to!(
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
// (32i64, true, 2u8),
// (i64, bool, u8)
// );
// }
// #[test]
// fn blue_and_present() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue, y: 7 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn blue_and_absent() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue }
// "#
// ),
// 3,
// i64
// );
// }
}

View File

@ -16,12 +16,13 @@ roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_std = { path = "../../roc_std" }
morphic_lib = { path = "../../vendor/morphic_lib" }
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"] }
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10"
target-lexicon = "0.12.2"
[dev-dependencies]
roc_can = { path = "../can" }
@ -29,7 +30,6 @@ roc_parse = { path = "../parse" }
roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"

View File

@ -1,3 +1,4 @@
use std::convert::TryFrom;
use std::path::Path;
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
@ -176,10 +177,22 @@ impl std::convert::TryFrom<u32> for PanicTagId {
}
impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
/// The integer type representing a pointer
///
/// on 64-bit systems, this is i64
/// on 32-bit systems, this is i32
pub fn ptr_int(&self) -> IntType<'ctx> {
ptr_int(self.context, self.ptr_bytes)
}
/// The integer type representing a RocList or RocStr when following the C ABI
///
/// on 64-bit systems, this is i128
/// on 32-bit systems, this is i64
pub fn str_list_c_abi(&self) -> IntType<'ctx> {
crate::llvm::convert::str_list_int(self.context, self.ptr_bytes)
}
pub fn small_str_bytes(&self) -> u32 {
self.ptr_bytes * 2
}
@ -370,10 +383,18 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
}
}
pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Module<'ctx> {
pub fn module_from_builtins<'ctx>(
ctx: &'ctx Context,
module_name: &str,
ptr_bytes: u32,
) -> Module<'ctx> {
// In the build script for the builtins module,
// we compile the builtins into LLVM bitcode
let bitcode_bytes: &[u8] = include_bytes!("../../../builtins/bitcode/builtins.bc");
let bitcode_bytes: &[u8] = match ptr_bytes {
8 => include_bytes!("../../../builtins/bitcode/builtins-64bit.bc"),
4 => include_bytes!("../../../builtins/bitcode/builtins-32bit.bc"),
_ => unreachable!(),
};
let memory_buffer = MemoryBuffer::create_from_memory_range(bitcode_bytes, module_name);
@ -689,11 +710,6 @@ pub fn float_with_precision<'a, 'ctx, 'env>(
precision: &Builtin,
) -> BasicValueEnum<'ctx> {
match precision {
Builtin::Decimal => call_bitcode_fn(
env,
&[env.context.f64_type().const_float(value).into()],
bitcode::DEC_FROM_F64,
),
Builtin::Float64 => env.context.f64_type().const_float(value).into(),
Builtin::Float32 => env.context.f32_type().const_float(value).into(),
_ => panic!("Invalid layout for float literal = {:?}", precision),
@ -718,6 +734,11 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
_ => panic!("Invalid layout for float literal = {:?}", layout),
},
Decimal(int) => env
.context
.i128_type()
.const_int(int.0 as u64, false)
.into(),
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => {
@ -927,11 +948,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
CallType::Foreign {
foreign_symbol,
ret_layout,
} => {
// we always initially invoke foreign symbols, but if there is nothing to clean up,
// we emit a normal call
build_foreign_symbol(env, scope, foreign_symbol, arguments, ret_layout)
}
} => build_foreign_symbol(env, scope, foreign_symbol, arguments, ret_layout),
}
}
@ -3931,6 +3948,8 @@ pub fn get_call_conventions(cc: target_lexicon::CallingConvention) -> u32 {
SystemV => C_CALL_CONV,
WasmBasicCAbi => C_CALL_CONV,
WindowsFastcall => C_CALL_CONV,
AppleAarch64 => C_CALL_CONV,
_ => C_CALL_CONV,
}
}
@ -4429,6 +4448,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
}
}
// TODO: Fix me! I should be different in tests vs. user code!
fn expect_failed() {
panic!("An expectation failed!");
}
#[allow(clippy::too_many_arguments)]
fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -4439,6 +4463,7 @@ fn run_low_level<'a, 'ctx, 'env>(
op: LowLevel,
args: &[Symbol],
update_mode: Option<UpdateMode>,
// expect_failed: *const (),
) -> BasicValueEnum<'ctx> {
use LowLevel::*;
@ -5157,8 +5182,20 @@ fn run_low_level<'a, 'ctx, 'env>(
bd.position_at_end(throw_block);
throw_exception(env, "assert failed!");
let fn_ptr_type = context
.void_type()
.fn_type(&[], false)
.ptr_type(AddressSpace::Generic);
let fn_addr = env
.ptr_int()
.const_int(expect_failed as *const () as u64, false);
let func: PointerValue<'ctx> =
bd.build_int_to_ptr(fn_addr, fn_ptr_type, "cast_expect_failed_addr_to_ptr");
let callable = CallableValue::try_from(func).unwrap();
bd.build_call(callable, &[], "call_expect_failed");
bd.build_unconditional_branch(then_block);
bd.position_at_end(then_block);
cond
@ -5170,89 +5207,171 @@ fn run_low_level<'a, 'ctx, 'env>(
}
}
fn build_foreign_symbol_return_result<'a, 'ctx, 'env>(
/// A type that is valid according to the C ABI
///
/// As an example, structs that fit inside an integer type should
/// (this does not currently happen here) be coerced to that integer type.
fn to_cc_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
return_type: BasicTypeEnum<'ctx>,
) -> (FunctionValue<'ctx>, &'a [BasicValueEnum<'ctx>]) {
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena);
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env, layout);
debug_assert_eq!(arg_type, value.get_type());
arg_types.push(arg_type);
layout: &Layout<'a>,
) -> BasicTypeEnum<'ctx> {
match layout {
Layout::Builtin(builtin) => to_cc_type_builtin(env, builtin),
_ => {
// TODO this is almost certainly incorrect for bigger structs
basic_type_from_layout(env, layout)
}
}
let function_type = return_type.fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
(function, arg_vals.into_bump_slice())
}
fn build_foreign_symbol_write_result_into_ptr<'a, 'ctx, 'env>(
fn to_cc_type_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
return_pointer: PointerValue<'ctx>,
) -> (FunctionValue<'ctx>, &'a [BasicValueEnum<'ctx>]) {
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena);
arg_vals.push(return_pointer.into());
arg_types.push(return_pointer.get_type().into());
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env, layout);
debug_assert_eq!(arg_type, value.get_type());
arg_types.push(arg_type);
builtin: &Builtin<'a>,
) -> BasicTypeEnum<'ctx> {
match builtin {
Builtin::Int128
| Builtin::Int64
| Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize
| Builtin::Decimal
| Builtin::Float128
| Builtin::Float64
| Builtin::Float32
| Builtin::Float16 => basic_type_from_builtin(env, builtin),
Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => {
env.str_list_c_abi().into()
}
Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::EmptyDict | Builtin::EmptySet => {
// TODO verify this is what actually happens
basic_type_from_builtin(env, builtin)
}
}
}
enum CCReturn {
/// Return as normal
Return,
/// require an extra argument, a pointer
/// where the result is written into
/// returns void
ByPointer,
/// The return type is zero-sized
Void,
}
/// According to the C ABI, how should we return a value with the given layout?
fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
let return_size = layout.stack_size(env.ptr_bytes);
let pass_result_by_pointer = return_size > 2 * env.ptr_bytes;
if return_size == 0 {
CCReturn::Void
} else if pass_result_by_pointer {
CCReturn::ByPointer
} else {
CCReturn::Return
}
let function_type = env.context.void_type().fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
(function, arg_vals.into_bump_slice())
}
#[allow(clippy::too_many_arguments)]
fn build_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
argument_symbols: &[Symbol],
ret_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let ret_type = basic_type_from_layout(env, ret_layout);
let return_pointer = env.builder.build_alloca(ret_type, "return_value");
let builder = env.builder;
let context = env.context;
// crude approximation of the C calling convention
let pass_result_by_pointer = ret_layout.stack_size(env.ptr_bytes) > 2 * env.ptr_bytes;
// Here we build two functions:
//
// - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine`
// This is just a type signature that we make available to the linker,
// and can use in the wrapper
// - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper`
let (function, arguments) = if pass_result_by_pointer {
build_foreign_symbol_write_result_into_ptr(env, scope, foreign, arguments, return_pointer)
} else {
build_foreign_symbol_return_result(env, scope, foreign, arguments, ret_type)
let return_type = basic_type_from_layout(env, ret_layout);
let cc_return = to_cc_return(env, ret_layout);
let mut cc_argument_types = Vec::with_capacity_in(argument_symbols.len() + 1, env.arena);
let mut fastcc_argument_types = Vec::with_capacity_in(argument_symbols.len(), env.arena);
let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena);
for symbol in argument_symbols {
let (value, layout) = load_symbol_and_layout(scope, symbol);
cc_argument_types.push(to_cc_type(env, layout));
let basic_type = basic_type_from_layout(env, layout);
fastcc_argument_types.push(basic_type);
arguments.push(value);
}
let cc_type = match cc_return {
CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false),
CCReturn::ByPointer => {
cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context.void_type().fn_type(&cc_argument_types, false)
}
CCReturn::Return => return_type.fn_type(&cc_argument_types, false),
};
let call = env.builder.build_call(function, arguments, "tmp");
let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type);
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
let fastcc_type = return_type.fn_type(&fastcc_argument_types, false);
call.try_as_basic_value();
let fastcc_function = add_func(
env.module,
&format!("{}_fastcc_wrapper", foreign.as_str()),
fastcc_type,
Linkage::Private,
FAST_CALL_CONV,
);
if pass_result_by_pointer {
env.builder.build_load(return_pointer, "read_result")
} else {
call.try_as_basic_value().left().unwrap()
let old = builder.get_insert_block().unwrap();
let entry = context.append_basic_block(fastcc_function, "entry");
{
builder.position_at_end(entry);
let return_pointer = env.builder.build_alloca(return_type, "return_value");
let fastcc_parameters = fastcc_function.get_params();
let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena);
for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) {
if param.get_type() == *cc_type {
cc_arguments.push(param);
} else {
let as_cc_type = complex_bitcast(env.builder, param, *cc_type, "to_cc_type");
cc_arguments.push(as_cc_type);
}
}
if let CCReturn::ByPointer = cc_return {
cc_arguments.push(return_pointer.into());
}
let call = env.builder.build_call(cc_function, &cc_arguments, "tmp");
call.set_call_convention(C_CALL_CONV);
let return_value = match cc_return {
CCReturn::Return => call.try_as_basic_value().left().unwrap(),
CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"),
CCReturn::Void => return_type.const_zero(),
};
builder.build_return(Some(&return_value));
}
builder.position_at_end(old);
let call = env.builder.build_call(fastcc_function, &arguments, "tmp");
call.set_call_convention(FAST_CALL_CONV);
return call.try_as_basic_value().left().unwrap();
}
fn throw_on_overflow<'a, 'ctx, 'env>(

View File

@ -6,42 +6,39 @@ use crate::llvm::build::{
complex_bitcast, load_symbol, load_symbol_and_layout, Env, RocFunctionCall, Scope,
};
use crate::llvm::build_list::{layout_width, pass_as_opaque};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::convert::{basic_type_from_layout, zig_dict_type, zig_list_type};
use crate::llvm::refcounting::Mode;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::context::Context;
use inkwell::types::BasicType;
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, StructValue};
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, StructValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds};
#[repr(u8)]
enum Alignment {
Align16KeyFirst = 0,
Align16ValueFirst = 1,
Align8KeyFirst = 2,
Align8ValueFirst = 3,
}
#[repr(transparent)]
struct Alignment(u8);
impl Alignment {
fn from_key_value_layout(key: &Layout, value: &Layout, ptr_bytes: u32) -> Alignment {
let key_align = key.alignment_bytes(ptr_bytes);
let value_align = value.alignment_bytes(ptr_bytes);
if key_align >= value_align {
match key_align.max(value_align) {
8 => Alignment::Align8KeyFirst,
16 => Alignment::Align16KeyFirst,
_ => unreachable!(),
}
} else {
match key_align.max(value_align) {
8 => Alignment::Align8ValueFirst,
16 => Alignment::Align16ValueFirst,
_ => unreachable!(),
}
let mut bits = key_align.max(value_align) as u8;
debug_assert!(bits == 4 || bits == 8 || bits == 16);
let value_before_key_flag = 0b1000_0000;
if key_align < value_align {
bits |= value_before_key_flag;
}
Alignment(bits)
}
fn as_int_value<'ctx>(&self, context: &'ctx Context) -> IntValue<'ctx> {
context.i8_type().const_int(self.0 as u64, false)
}
}
@ -59,12 +56,11 @@ pub fn dict_len<'a, 'ctx, 'env>(
// let dict_as_int = dict_symbol_to_i128(env, scope, dict_symbol);
let dict_as_zig_dict = dict_symbol_to_zig_dict(env, scope, dict_symbol);
let dict_ptr = env
.builder
.build_alloca(dict_as_zig_dict.get_type(), "dict_ptr");
env.builder.build_store(dict_ptr, dict_as_zig_dict);
call_bitcode_fn(env, &[dict_ptr.into()], bitcode::DICT_LEN)
call_bitcode_fn(
env,
&[pass_dict_c_abi(env, dict_as_zig_dict.into())],
bitcode::DICT_LEN,
)
}
Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(),
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout),
@ -95,14 +91,11 @@ pub fn dict_insert<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
let value_ptr = builder.build_alloca(value.get_type(), "value_ptr");
env.builder.build_store(dict_ptr, dict);
env.builder.build_store(key_ptr, key);
env.builder.build_store(value_ptr, value);
@ -114,10 +107,10 @@ pub fn dict_insert<'a, 'ctx, 'env>(
.ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let result_ptr = builder.build_alloca(zig_dict_type, "result_ptr");
let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr");
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -128,7 +121,7 @@ pub fn dict_insert<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
@ -157,13 +150,10 @@ pub fn dict_remove<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(dict_ptr, dict);
env.builder.build_store(key_ptr, key);
let key_width = env
@ -174,10 +164,10 @@ pub fn dict_remove<'a, 'ctx, 'env>(
.ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let result_ptr = builder.build_alloca(zig_dict_type, "result_ptr");
let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr");
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -188,7 +178,7 @@ pub fn dict_remove<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
@ -216,13 +206,10 @@ pub fn dict_contains<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(dict_ptr, dict);
env.builder.build_store(key_ptr, key);
let key_width = env
@ -234,7 +221,7 @@ pub fn dict_contains<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -242,7 +229,7 @@ pub fn dict_contains<'a, 'ctx, 'env>(
call_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
@ -265,13 +252,10 @@ pub fn dict_get<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(dict_ptr, dict);
env.builder.build_store(key_ptr, key);
let key_width = env
@ -283,7 +267,7 @@ pub fn dict_get<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -294,7 +278,7 @@ pub fn dict_get<'a, 'ctx, 'env>(
let result = call_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
@ -376,13 +360,6 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
value_layout: &Layout<'a>,
rc_operation: Mode,
) {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(dict_ptr, dict);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -392,7 +369,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let (key_fn, value_fn) = match rc_operation {
Mode::Inc => (
@ -408,7 +385,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
alignment_iv.into(),
key_width.into(),
value_width.into(),
@ -429,12 +406,6 @@ pub fn dict_keys<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let zig_list_type = env.module.get_struct_type("list.RocList").unwrap();
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(dict_ptr, dict);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -444,16 +415,16 @@ pub fn dict_keys<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let list_ptr = builder.build_alloca(zig_list_type, "list_ptr");
let list_ptr = builder.build_alloca(zig_list_type(env), "list_ptr");
call_void_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
alignment_iv.into(),
key_width.into(),
value_width.into(),
@ -475,6 +446,26 @@ pub fn dict_keys<'a, 'ctx, 'env>(
env.builder.build_load(list_ptr, "load_keys_list")
}
fn pass_dict_c_abi<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
dict: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
match env.ptr_bytes {
4 => {
let target_type = env.context.custom_width_int_type(96).into();
complex_bitcast(env.builder, dict, target_type, "to_i96")
}
8 => {
let dict_ptr = env.builder.build_alloca(zig_dict_type(env), "dict_ptr");
env.builder.build_store(dict_ptr, dict);
dict_ptr.into()
}
_ => unreachable!(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn dict_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -486,14 +477,6 @@ pub fn dict_union<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let dict1_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let dict2_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(dict1_ptr, dict1);
env.builder.build_store(dict2_ptr, dict2);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -503,7 +486,7 @@ pub fn dict_union<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -511,13 +494,13 @@ pub fn dict_union<'a, 'ctx, 'env>(
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
let output_ptr = builder.build_alloca(zig_dict_type, "output_ptr");
let output_ptr = builder.build_alloca(zig_dict_type(env), "output_ptr");
call_void_bitcode_fn(
env,
&[
dict1_ptr.into(),
dict2_ptr.into(),
pass_dict_c_abi(env, dict1),
pass_dict_c_abi(env, dict2),
alignment_iv.into(),
key_width.into(),
value_width.into(),
@ -587,12 +570,6 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let dict1_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let dict2_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(dict1_ptr, dict1);
env.builder.build_store(dict2_ptr, dict2);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -602,7 +579,7 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -615,8 +592,8 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
dict1_ptr.into(),
dict2_ptr.into(),
pass_dict_c_abi(env, dict1),
pass_dict_c_abi(env, dict2),
alignment_iv.into(),
key_width.into(),
value_width.into(),
@ -645,24 +622,20 @@ pub fn dict_walk<'a, 'ctx, 'env>(
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(dict_ptr, dict);
let accum_bt = basic_type_from_layout(env, accum_layout);
let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr");
env.builder.build_store(accum_ptr, accum);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let output_ptr = builder.build_alloca(accum_bt, "output_ptr");
call_void_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -690,12 +663,8 @@ pub fn dict_values<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = super::convert::zig_dict_type(env);
let zig_list_type = super::convert::zig_list_type(env);
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(dict_ptr, dict);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -705,7 +674,7 @@ pub fn dict_values<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
@ -714,7 +683,7 @@ pub fn dict_values<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
dict_ptr.into(),
pass_dict_c_abi(env, dict),
alignment_iv.into(),
key_width.into(),
value_width.into(),
@ -748,7 +717,7 @@ pub fn set_from_list<'a, 'ctx, 'env>(
let list_alloca = builder.build_alloca(list.get_type(), "list_alloca");
let list_ptr = env.builder.build_bitcast(
list_alloca,
env.context.i128_type().ptr_type(AddressSpace::Generic),
env.str_list_c_abi().ptr_type(AddressSpace::Generic),
"to_zig_list",
);
@ -764,7 +733,7 @@ pub fn set_from_list<'a, 'ctx, 'env>(
let alignment =
Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -865,11 +834,11 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
) -> StructValue<'ctx> {
let dict = load_symbol(scope, &symbol);
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
complex_bitcast(env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict").into_struct_value()
}
fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> {
env.module.get_struct_type("dict.RocDict").unwrap()
complex_bitcast(
env.builder,
dict,
crate::llvm::convert::zig_dict_type(env).into(),
"dict_to_zig_dict",
)
.into_struct_value()
}

View File

@ -133,7 +133,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
// let zig deal with big vs small string
call_bitcode_fn(
env,
&[seed.into(), build_str::str_to_i128(env, val).into()],
&[seed.into(), build_str::str_to_c_abi(env, val).into()],
bitcode::DICT_HASH_STR,
)
.into_int_value()
@ -785,15 +785,7 @@ fn hash_list<'a, 'ctx, 'env>(
env.builder.build_store(result, answer);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
length,
"current_index",
loop_fn,
);
incrementing_elem_loop(env, parent, ptr, length, "current_index", loop_fn);
env.builder.build_unconditional_branch(done_block);
@ -886,7 +878,7 @@ fn hash_bitcode_fn<'a, 'ctx, 'env>(
buffer: PointerValue<'ctx>,
width: u32,
) -> IntValue<'ctx> {
let num_bytes = env.context.i64_type().const_int(width as u64, false);
let num_bytes = env.ptr_int().const_int(width as u64, false);
call_bitcode_fn(
env,

View File

@ -21,12 +21,12 @@ fn list_returned_from_zig<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
output: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
// per the C ABI, our list objects are passed between functions as an i128
// per the C ABI, our list objects are passed between functions as an i128/i64
complex_bitcast(
env.builder,
output,
super::convert::zig_list_type(env).into(),
"from_i128",
"from_str_list_int",
)
}
@ -54,11 +54,16 @@ fn pass_element_as_opaque<'a, 'ctx, 'env>(
)
}
fn pass_list_as_i128<'a, 'ctx, 'env>(
fn pass_list_cc<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
list: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128")
complex_bitcast(
env.builder,
list,
env.str_list_c_abi().into(),
"to_str_list_int",
)
}
pub fn layout_width<'a, 'ctx, 'env>(
@ -139,7 +144,7 @@ pub fn list_join<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, outer_list),
pass_list_cc(env, outer_list),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
],
@ -172,7 +177,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout),
],
@ -227,7 +232,7 @@ pub fn list_append<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, original_wrapper.into()),
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
@ -246,7 +251,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, original_wrapper.into()),
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
@ -266,7 +271,7 @@ pub fn list_swap<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, original_wrapper.into()),
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
index_1.into(),
@ -288,7 +293,7 @@ pub fn list_drop<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, original_wrapper.into()),
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
count.into(),
@ -406,7 +411,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -437,7 +442,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -539,7 +544,7 @@ pub fn list_contains<'a, 'ctx, 'env>(
call_bitcode_fn(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
eq_fn,
@ -562,7 +567,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -604,7 +609,7 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
call_bitcode_fn(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -648,7 +653,7 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
call_bitcode_fn(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -675,7 +680,7 @@ pub fn list_sort_with<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
compare_wrapper.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -698,7 +703,7 @@ pub fn list_map_with_index<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -722,7 +727,7 @@ pub fn list_map<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -751,8 +756,8 @@ pub fn list_map2<'a, 'ctx, 'env>(
call_bitcode_fn(
env,
&[
pass_list_as_i128(env, list1),
pass_list_as_i128(env, list2),
pass_list_cc(env, list1),
pass_list_cc(env, list2),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -787,9 +792,9 @@ pub fn list_map3<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list1),
pass_list_as_i128(env, list2),
pass_list_as_i128(env, list3),
pass_list_cc(env, list1),
pass_list_cc(env, list2),
pass_list_cc(env, list3),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
@ -824,8 +829,8 @@ pub fn list_concat<'a, 'ctx, 'env>(
Layout::Builtin(Builtin::List(elem_layout)) => call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, first_list),
pass_list_as_i128(env, second_list),
pass_list_cc(env, first_list),
pass_list_cc(env, second_list),
env.alignment_intvalue(elem_layout),
layout_width(env, elem_layout),
],
@ -913,9 +918,8 @@ where
index_alloca
}
pub fn incrementing_elem_loop<'ctx, LoopFn>(
builder: &Builder<'ctx>,
ctx: &'ctx Context,
pub fn incrementing_elem_loop<'a, 'ctx, 'env, LoopFn>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
ptr: PointerValue<'ctx>,
len: IntValue<'ctx>,
@ -925,7 +929,9 @@ pub fn incrementing_elem_loop<'ctx, LoopFn>(
where
LoopFn: FnMut(IntValue<'ctx>, BasicValueEnum<'ctx>),
{
incrementing_index_loop(builder, ctx, parent, len, index_name, |index| {
let builder = env.builder;
incrementing_index_loop(env, parent, len, index_name, |index| {
// The pointer to the element in the list
let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") };
@ -937,9 +943,8 @@ where
// This helper simulates a basic for loop, where
// and index increments up from 0 to some end value
pub fn incrementing_index_loop<'ctx, LoopFn>(
builder: &Builder<'ctx>,
ctx: &'ctx Context,
pub fn incrementing_index_loop<'a, 'ctx, 'env, LoopFn>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
end: IntValue<'ctx>,
index_name: &str,
@ -948,12 +953,15 @@ pub fn incrementing_index_loop<'ctx, LoopFn>(
where
LoopFn: FnMut(IntValue<'ctx>),
{
let ctx = env.context;
let builder = env.builder;
// constant 1i64
let one = ctx.i64_type().const_int(1, false);
let one = env.ptr_int().const_int(1, false);
// allocate a stack slot for the current index
let index_alloca = builder.build_alloca(ctx.i64_type(), index_name);
builder.build_store(index_alloca, ctx.i64_type().const_zero());
let index_alloca = builder.build_alloca(env.ptr_int(), index_name);
builder.build_store(index_alloca, env.ptr_int().const_zero());
let loop_bb = ctx.append_basic_block(parent, "loop");
builder.build_unconditional_branch(loop_bb);

View File

@ -21,12 +21,12 @@ pub fn str_split<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let delim_i128 = str_symbol_to_i128(env, scope, delimiter_symbol);
let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol);
let delim_c_abi = str_symbol_to_c_abi(env, scope, delimiter_symbol);
let segment_count = call_bitcode_fn(
env,
&[str_i128.into(), delim_i128.into()],
&[str_c_abi.into(), delim_c_abi.into()],
bitcode::STR_COUNT_SEGMENTS,
)
.into_int_value();
@ -46,26 +46,34 @@ pub fn str_split<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[ret_list_ptr_zig_rocstr, str_i128.into(), delim_i128.into()],
&[
ret_list_ptr_zig_rocstr,
str_c_abi.into(),
delim_c_abi.into(),
],
bitcode::STR_STR_SPLIT_IN_PLACE,
);
store_list(env, ret_list_ptr, segment_count)
}
fn str_symbol_to_i128<'a, 'ctx, 'env>(
fn str_symbol_to_c_abi<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
symbol: Symbol,
) -> IntValue<'ctx> {
let string = load_symbol(scope, &symbol);
let i128_type = env.context.i128_type().into();
let target_type = match env.ptr_bytes {
8 => env.context.i128_type().into(),
4 => env.context.i64_type().into(),
_ => unreachable!(),
};
complex_bitcast(env.builder, string, i128_type, "str_to_i128").into_int_value()
complex_bitcast(env.builder, string, target_type, "str_to_c_abi").into_int_value()
}
pub fn str_to_i128<'a, 'ctx, 'env>(
pub fn str_to_c_abi<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> {
@ -73,17 +81,19 @@ pub fn str_to_i128<'a, 'ctx, 'env>(
env.builder.build_store(cell, value);
let i128_ptr = env
let target_type = match env.ptr_bytes {
8 => env.context.i128_type(),
4 => env.context.i64_type(),
_ => unreachable!(),
};
let target_type_ptr = env
.builder
.build_bitcast(
cell,
env.context.i128_type().ptr_type(AddressSpace::Generic),
"cast",
)
.build_bitcast(cell, target_type.ptr_type(AddressSpace::Generic), "cast")
.into_pointer_value();
env.builder
.build_load(i128_ptr, "load_as_i128")
.build_load(target_type_ptr, "load_as_c_abi")
.into_int_value()
}
@ -113,12 +123,12 @@ pub fn str_concat<'a, 'ctx, 'env>(
str2_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
// swap the arguments; second argument comes before the second in the output string
let str1_i128 = str_symbol_to_i128(env, scope, str1_symbol);
let str2_i128 = str_symbol_to_i128(env, scope, str2_symbol);
let str1_c_abi = str_symbol_to_c_abi(env, scope, str1_symbol);
let str2_c_abi = str_symbol_to_c_abi(env, scope, str2_symbol);
call_bitcode_fn(
env,
&[str1_i128.into(), str2_i128.into()],
&[str1_c_abi.into(), str2_c_abi.into()],
bitcode::STR_CONCAT,
)
}
@ -132,8 +142,8 @@ pub fn str_join_with<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
// dirty hack; pretend a `list` is a `str` that works because
// they have the same stack layout `{ u8*, usize }`
let list_i128 = str_symbol_to_i128(env, scope, list_symbol);
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let list_i128 = str_symbol_to_c_abi(env, scope, list_symbol);
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
call_bitcode_fn(
env,
@ -147,7 +157,7 @@ pub fn str_number_of_bytes<'a, 'ctx, 'env>(
scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
) -> IntValue<'ctx> {
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
// the builtin will always return an u64
let length =
@ -165,8 +175,8 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
str_symbol: Symbol,
prefix_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let prefix_i128 = str_symbol_to_i128(env, scope, prefix_symbol);
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
let prefix_i128 = str_symbol_to_c_abi(env, scope, prefix_symbol);
call_bitcode_fn(
env,
@ -182,7 +192,7 @@ pub fn str_starts_with_code_point<'a, 'ctx, 'env>(
str_symbol: Symbol,
prefix_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
let prefix = load_symbol(scope, &prefix_symbol);
call_bitcode_fn(
@ -199,8 +209,8 @@ pub fn str_ends_with<'a, 'ctx, 'env>(
str_symbol: Symbol,
prefix_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let prefix_i128 = str_symbol_to_i128(env, scope, prefix_symbol);
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
let prefix_i128 = str_symbol_to_c_abi(env, scope, prefix_symbol);
call_bitcode_fn(
env,
@ -215,7 +225,7 @@ pub fn str_count_graphemes<'a, 'ctx, 'env>(
scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
call_bitcode_fn(
env,
@ -243,7 +253,7 @@ pub fn str_to_utf8<'a, 'ctx, 'env>(
let string = complex_bitcast(
env.builder,
original_wrapper.into(),
env.context.i128_type().into(),
env.str_list_c_abi().into(),
"to_utf8",
);
@ -269,14 +279,13 @@ pub fn str_from_utf8_range<'a, 'ctx, 'env>(
complex_bitcast(
env.builder,
list_wrapper.into(),
env.context.i128_type().into(),
env.str_list_c_abi().into(),
"to_i128",
),
// TODO: This won't work for 32 bit targets!
complex_bitcast(
env.builder,
count_and_start.into(),
env.context.i128_type().into(),
env.str_list_c_abi().into(),
"to_i128",
),
result_ptr.into(),
@ -324,7 +333,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>(
complex_bitcast(
env.builder,
original_wrapper.into(),
env.context.i128_type().into(),
env.str_list_c_abi().into(),
"to_i128",
),
result_ptr.into(),
@ -371,8 +380,8 @@ pub fn str_equal<'a, 'ctx, 'env>(
value1: BasicValueEnum<'ctx>,
value2: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let str1_i128 = str_to_i128(env, value1);
let str2_i128 = str_to_i128(env, value2);
let str1_i128 = str_to_c_abi(env, value1);
let str2_i128 = str_to_c_abi(env, value2);
call_bitcode_fn(
env,

View File

@ -465,8 +465,8 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
let end = len1;
// allocate a stack slot for the current index
let index_alloca = builder.build_alloca(ctx.i64_type(), "index");
builder.build_store(index_alloca, ctx.i64_type().const_zero());
let index_alloca = builder.build_alloca(env.ptr_int(), "index");
builder.build_store(index_alloca, env.ptr_int().const_zero());
let loop_bb = ctx.append_basic_block(parent, "loop");
let body_bb = ctx.append_basic_block(parent, "body");
@ -521,8 +521,8 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
{
env.builder.position_at_end(increment_bb);
// constant 1i64
let one = ctx.i64_type().const_int(1, false);
// constant 1isize
let one = env.ptr_int().const_int(1, false);
let next_index = builder.build_int_add(curr_index, one, "nextindex");

View File

@ -194,6 +194,20 @@ pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {
}
}
/// The int type that the C ABI turns our RocList/RocStr into
pub fn str_list_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {
match ptr_bytes {
1 => ctx.i16_type(),
2 => ctx.i32_type(),
4 => ctx.i64_type(),
8 => ctx.i128_type(),
_ => panic!(
"Invalid target: Roc does't support compiling to {}-bit systems.",
ptr_bytes * 8
),
}
}
pub fn zig_dict_type<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
) -> StructType<'ctx> {

View File

@ -1,7 +1,8 @@
use crate::debug_info_init;
use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX,
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I32,
LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX,
};
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{basic_type_from_layout, ptr_int};
@ -170,7 +171,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
None => {
// inc and dec return void
let fn_type = context.void_type().fn_type(
&[context.i64_type().ptr_type(AddressSpace::Generic).into()],
&[env.ptr_int().ptr_type(AddressSpace::Generic).into()],
false,
);
@ -243,6 +244,12 @@ impl<'ctx> PointerToRefcount<'ctx> {
builder.build_conditional_branch(is_static_allocation, return_block, branch_block);
let add_intrinsic = match env.ptr_bytes {
8 => LLVM_SADD_WITH_OVERFLOW_I64,
4 => LLVM_SADD_WITH_OVERFLOW_I32,
_ => unreachable!(),
};
let add_with_overflow;
{
@ -250,7 +257,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
add_with_overflow = env
.call_intrinsic(
LLVM_SADD_WITH_OVERFLOW_I64,
add_intrinsic,
&[
refcount.into(),
refcount_type.const_int(-1_i64 as u64, true).into(),
@ -774,7 +781,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
let is_non_empty = builder.build_int_compare(
IntPredicate::UGT,
len,
ctx.i64_type().const_zero(),
env.ptr_int().const_zero(),
"len > 0",
);
@ -803,15 +810,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn);
}
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);

View File

@ -832,6 +832,7 @@ struct State<'a> {
pub exposed_types: SubsByModule,
pub output_path: Option<&'a str>,
pub platform_path: PlatformPath<'a>,
pub ptr_bytes: u32,
pub headers_parsed: MutSet<ModuleId>,
@ -1467,6 +1468,7 @@ where
let mut state = State {
root_id,
ptr_bytes,
platform_data: None,
goal_phase,
stdlib,
@ -1978,7 +1980,10 @@ fn update<'a>(
);
if state.goal_phase > Phase::SolveTypes {
let layout_cache = state.layout_caches.pop().unwrap_or_default();
let layout_cache = state
.layout_caches
.pop()
.unwrap_or_else(|| LayoutCache::new(state.ptr_bytes));
let typechecked = TypeCheckedModule {
module_id,

View File

@ -786,6 +786,9 @@ define_builtins! {
// used to initialize parameters in borrow.rs
22 EMPTY_PARAM: "#empty_param"
// used by the dev backend to store the pointer to where to store large return types
23 RET_POINTER: "#ret_pointer"
}
1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias

View File

@ -13,6 +13,7 @@ roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_std = { path = "../../roc_std" }
roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" }
morphic_lib = { path = "../../vendor/morphic_lib" }

View File

@ -1169,7 +1169,7 @@ fn literal_spec(
match literal {
Str(_) => new_static_string(builder, block),
Int(_) | Float(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]),
Int(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]),
}
}

View File

@ -7,6 +7,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_std::RocDec;
/// COMPILE CASES
@ -85,8 +86,8 @@ enum Test<'a> {
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
},
IsInt(i128),
// float patterns are stored as u64 so they are comparable/hashable
IsFloat(u64),
IsDecimal(RocDec),
IsStr(Box<str>),
IsBit(bool),
IsByte {
@ -126,6 +127,11 @@ impl<'a> Hash for Test<'a> {
tag_id.hash(state);
num_alts.hash(state);
}
IsDecimal(v) => {
// TODO: Is this okay?
state.write_u8(6);
v.0.hash(state);
}
}
}
}
@ -302,6 +308,7 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool {
Test::IsBit(_) => number_of_tests == 2,
Test::IsInt(_) => false,
Test::IsFloat(_) => false,
Test::IsDecimal(_) => false,
Test::IsStr(_) => false,
}
}
@ -556,6 +563,7 @@ fn test_at_path<'a>(
},
IntLiteral(v) => IsInt(*v),
FloatLiteral(v) => IsFloat(*v),
DecimalLiteral(v) => IsDecimal(*v),
StrLiteral(v) => IsStr(v.clone()),
};
@ -823,6 +831,18 @@ fn to_relevant_branch_help<'a>(
_ => None,
},
DecimalLiteral(dec) => match test {
IsDecimal(test_dec) if dec.0 == test_dec.0 => {
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
_ => None,
},
BitLiteral { value: bit, .. } => match test {
IsBit(test_bit) if bit == *test_bit => {
start.extend(end);
@ -910,6 +930,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
| EnumLiteral { .. }
| IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| StrLiteral(_) => true,
}
}
@ -1279,6 +1300,14 @@ fn test_to_equality<'a>(
(stores, lhs_symbol, rhs_symbol, None)
}
Test::IsDecimal(test_dec) => {
let lhs = Expr::Literal(Literal::Int(test_dec.0));
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int128), lhs));
(stores, lhs_symbol, rhs_symbol, None)
}
Test::IsByte {
tag_id: test_byte, ..
} => {

View File

@ -2,6 +2,7 @@ use crate::ir::DestructType;
use roc_collections::all::{Index, MutMap};
use roc_module::ident::{Lowercase, TagName};
use roc_region::all::{Located, Region};
use roc_std::RocDec;
use self::Pattern::*;
@ -56,6 +57,7 @@ pub enum Literal {
Bit(bool),
Byte(u8),
Float(u64),
Decimal(RocDec),
Str(Box<str>),
}
@ -65,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
match pattern {
IntLiteral(v) => Literal(Literal::Int(*v)),
FloatLiteral(v) => Literal(Literal::Float(*v)),
DecimalLiteral(v) => Literal(Literal::Decimal(*v)),
StrLiteral(v) => Literal(Literal::Str(v.clone())),
// To make sure these are exhaustive, we have to "fake" a union here

View File

@ -14,6 +14,7 @@ use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Located, Region};
use roc_std::RocDec;
use roc_types::solved_types::SolvedType;
use roc_types::subs::{Content, FlatType, Subs, Variable, VariableSubsSlice};
use std::collections::HashMap;
@ -1024,6 +1025,7 @@ pub enum Literal<'a> {
// Literals
Int(i128),
Float(f64),
Decimal(RocDec),
Str(&'a str),
/// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool,
/// so they can (at least potentially) be emitted as 1-bit machine bools.
@ -1205,6 +1207,8 @@ impl<'a> Literal<'a> {
match self {
Int(lit) => alloc.text(format!("{}i64", lit)),
Float(lit) => alloc.text(format!("{}f64", lit)),
// TODO: Add proper Dec.to_str
Decimal(lit) => alloc.text(format!("{}Dec", lit.0)),
Bool(lit) => alloc.text(format!("{}", lit)),
Byte(lit) => alloc.text(format!("{}u8", lit)),
Str(lit) => alloc.text(format!("{:?}", lit)),
@ -1702,7 +1706,7 @@ fn pattern_to_when<'a>(
(symbol, Located::at_zero(wrapped_body))
}
IntLiteral(_, _) | NumLiteral(_, _) | FloatLiteral(_, _) | StrLiteral(_) => {
IntLiteral(_, _, _) | NumLiteral(_, _, _) | FloatLiteral(_, _, _) | StrLiteral(_) => {
// These patters are refutable, and thus should never occur outside a `when` expression
// They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
@ -2738,17 +2742,17 @@ pub fn with_hole<'a>(
let arena = env.arena;
match can_expr {
Int(_, precision, num) => {
Int(_, precision, _, int) => {
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, false) {
IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num)),
Expr::Literal(Literal::Int(int)),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num)),
Expr::Literal(Literal::Int(int)),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
@ -2756,20 +2760,26 @@ pub fn with_hole<'a>(
}
}
Float(_, precision, num) => {
Float(_, precision, float_str, float) => {
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, true) {
IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Float(num as f64)),
Expr::Literal(Literal::Float(float)),
Layout::Builtin(float_precision_to_builtin(precision)),
hole,
),
IntOrFloat::DecimalFloatType => Stmt::Let(
assigned,
Expr::Literal(Literal::Float(num as f64)),
Layout::Builtin(Builtin::Decimal),
hole,
),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(&float_str) {
Some(d) => d,
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", float_str),
};
Stmt::Let(
assigned,
Expr::Literal(Literal::Decimal(dec)),
Layout::Builtin(Builtin::Decimal),
hole,
)
}
_ => unreachable!("unexpected float precision for integer"),
}
}
@ -2781,9 +2791,8 @@ pub fn with_hole<'a>(
hole,
),
Num(var, num) => {
Num(var, num_str, num) => {
// first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) {
IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned,
@ -2803,12 +2812,18 @@ pub fn with_hole<'a>(
Layout::Builtin(float_precision_to_builtin(precision)),
hole,
),
IntOrFloat::DecimalFloatType => Stmt::Let(
assigned,
Expr::Literal(Literal::Float(num as f64)),
Layout::Builtin(Builtin::Decimal),
hole,
),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(&num_str) {
Some(d) => d,
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", num_str),
};
Stmt::Let(
assigned,
Expr::Literal(Literal::Decimal(dec)),
Layout::Builtin(Builtin::Decimal),
hole,
)
}
}
}
LetNonRec(def, cont, _) => {
@ -3109,7 +3124,8 @@ pub fn with_hole<'a>(
mut fields,
..
} => {
let sorted_fields = crate::layout::sort_record_fields(env.arena, record_var, env.subs);
let sorted_fields =
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes);
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
@ -3444,7 +3460,8 @@ pub fn with_hole<'a>(
loc_expr,
..
} => {
let sorted_fields = crate::layout::sort_record_fields(env.arena, record_var, env.subs);
let sorted_fields =
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes);
let mut index = None;
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -3586,7 +3603,8 @@ pub fn with_hole<'a>(
// This has the benefit that we don't need to do anything special for reference
// counting
let sorted_fields = crate::layout::sort_record_fields(env.arena, record_var, env.subs);
let sorted_fields =
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes);
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -4190,7 +4208,8 @@ fn convert_tag_union<'a>(
arena: &'a Bump,
) -> Stmt<'a> {
use crate::layout::UnionVariant::*;
let res_variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs);
let res_variant =
crate::layout::union_sorted_tags(env.arena, variant_var, env.subs, env.ptr_bytes);
let variant = match res_variant {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
@ -4526,7 +4545,7 @@ fn sorted_field_symbols<'a>(
}
};
let alignment = layout.alignment_bytes(8);
let alignment = layout.alignment_bytes(env.ptr_bytes);
let symbol = possible_reuse_symbol(env, procs, &arg.value);
field_symbols_temp.push((alignment, symbol, ((var, arg), &*env.arena.alloc(symbol))));
@ -5573,6 +5592,7 @@ fn store_pattern_help<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
@ -5707,6 +5727,7 @@ fn store_tag_pattern<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
@ -5782,6 +5803,7 @@ fn store_newtype_pattern<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
@ -5857,6 +5879,7 @@ fn store_record_destruct<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
@ -6828,6 +6851,7 @@ pub enum Pattern<'a> {
Underscore,
IntLiteral(i128),
FloatLiteral(u64),
DecimalLiteral(RocDec),
BitLiteral {
value: bool,
tag_name: TagName,
@ -6904,8 +6928,26 @@ fn from_can_pattern_help<'a>(
match can_pattern {
Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
IntLiteral(_, int) => Ok(Pattern::IntLiteral(*int as i128)),
FloatLiteral(_, float) => Ok(Pattern::FloatLiteral(f64::to_bits(*float))),
IntLiteral(_, _, int) => Ok(Pattern::IntLiteral(*int as i128)),
FloatLiteral(var, float_str, float) => {
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true?
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, true) {
IntOrFloat::SignedIntType(_) => {
panic!("Invalid percision for float literal = {:?}", var)
}
IntOrFloat::UnsignedIntType(_) => {
panic!("Invalid percision for float literal = {:?}", var)
}
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(f64::to_bits(*float))),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(float_str) {
Some(d) => d,
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", float_str),
};
Ok(Pattern::DecimalLiteral(dec))
}
}
}
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
Shadowed(region, ident) => Err(RuntimeError::Shadowing {
original_region: *region,
@ -6916,12 +6958,18 @@ fn from_can_pattern_help<'a>(
// TODO preserve malformed problem information here?
Err(RuntimeError::UnsupportedPattern(*region))
}
NumLiteral(var, num) => {
NumLiteral(var, num_str, num) => {
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) {
IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)),
IntOrFloat::DecimalFloatType => Ok(Pattern::FloatLiteral(*num as u64)),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", num_str),
};
Ok(Pattern::DecimalLiteral(dec))
}
}
}
@ -6934,7 +6982,8 @@ fn from_can_pattern_help<'a>(
use crate::exhaustive::Union;
use crate::layout::UnionVariant::*;
let res_variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs);
let res_variant =
crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.ptr_bytes);
let variant = match res_variant {
Ok(cached) => cached,
@ -7353,7 +7402,8 @@ fn from_can_pattern_help<'a>(
..
} => {
// sorted fields based on the type
let sorted_fields = crate::layout::sort_record_fields(env.arena, *whole_var, env.subs);
let sorted_fields =
crate::layout::sort_record_fields(env.arena, *whole_var, env.subs, env.ptr_bytes);
// sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
@ -7494,7 +7544,9 @@ fn from_can_record_destruct<'a>(
})
}
#[derive(Debug)]
pub enum IntPrecision {
Usize,
I128,
I64,
I32,
@ -7530,6 +7582,7 @@ fn int_precision_to_builtin(precision: IntPrecision) -> Builtin<'static> {
I32 => Builtin::Int32,
I16 => Builtin::Int16,
I8 => Builtin::Int8,
Usize => Builtin::Usize,
}
}
@ -7628,16 +7681,8 @@ pub fn num_argument_to_int_or_float(
Content::Alias(Symbol::NUM_NAT, _, _)
| Content::Alias(Symbol::NUM_NATURAL, _, _)
| Content::Alias(Symbol::NUM_AT_NATURAL, _, _) => {
match ptr_bytes {
1 => IntOrFloat::UnsignedIntType(IntPrecision::I8),
2 => IntOrFloat::UnsignedIntType(IntPrecision::I16),
4 => IntOrFloat::UnsignedIntType(IntPrecision::I32),
8 => IntOrFloat::UnsignedIntType(IntPrecision::I64),
_ => panic!(
"Invalid target for Num type argument: Roc does't support compiling to {}-bit systems.",
ptr_bytes * 8
),
}
IntOrFloat::UnsignedIntType(IntPrecision::Usize)
}
other => {
panic!(

View File

@ -138,7 +138,8 @@ impl<'a> RawFunctionLayout<'a> {
let fn_args = fn_args.into_bump_slice();
let ret = arena.alloc(ret);
let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var)?;
let lambda_set =
LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?;
Ok(Self::Function(fn_args, lambda_set, ret))
}
@ -516,6 +517,7 @@ impl<'a> LambdaSet<'a> {
arena: &'a Bump,
subs: &Subs,
closure_var: Variable,
ptr_bytes: u32,
) -> Result<Self, LayoutProblem> {
let mut tags = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) {
@ -529,6 +531,7 @@ impl<'a> LambdaSet<'a> {
arena,
subs,
seen: Vec::new_in(arena),
ptr_bytes,
};
for (tag_name, variables) in tags.iter() {
@ -545,7 +548,8 @@ impl<'a> LambdaSet<'a> {
}
}
let representation = arena.alloc(Self::make_representation(arena, subs, tags));
let representation =
arena.alloc(Self::make_representation(arena, subs, tags, ptr_bytes));
Ok(LambdaSet {
set: set.into_bump_slice(),
@ -568,9 +572,10 @@ impl<'a> LambdaSet<'a> {
arena: &'a Bump,
subs: &Subs,
tags: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
ptr_bytes: u32,
) -> Layout<'a> {
// otherwise, this is a closure with a payload
let variant = union_sorted_tags_help(arena, tags, None, subs);
let variant = union_sorted_tags_help(arena, tags, None, subs, ptr_bytes);
use UnionVariant::*;
match variant {
@ -648,6 +653,7 @@ pub enum Builtin<'a> {
}
pub struct Env<'a, 'b> {
ptr_bytes: u32,
arena: &'a Bump,
seen: Vec<'a, Variable>,
subs: &'b Subs,
@ -972,8 +978,9 @@ impl<'a> Layout<'a> {
/// e.g. `identity : a -> a` could be specialized to `Bool -> Bool` or `Str -> Str`.
/// Therefore in general it's invalid to store a map from variables to layouts
/// But if we're careful when to invalidate certain keys, we still get some benefit
#[derive(Default, Debug)]
#[derive(Debug)]
pub struct LayoutCache<'a> {
ptr_bytes: u32,
_marker: std::marker::PhantomData<&'a u8>,
}
@ -985,6 +992,13 @@ pub enum CachedLayout<'a> {
}
impl<'a> LayoutCache<'a> {
pub fn new(ptr_bytes: u32) -> Self {
Self {
ptr_bytes,
_marker: Default::default(),
}
}
pub fn from_var(
&mut self,
arena: &'a Bump,
@ -998,6 +1012,7 @@ impl<'a> LayoutCache<'a> {
arena,
subs,
seen: Vec::new_in(arena),
ptr_bytes: self.ptr_bytes,
};
Layout::from_var(&mut env, var)
@ -1016,6 +1031,7 @@ impl<'a> LayoutCache<'a> {
arena,
subs,
seen: Vec::new_in(arena),
ptr_bytes: self.ptr_bytes,
};
RawFunctionLayout::from_var(&mut env, var)
@ -1038,7 +1054,6 @@ impl<'a> Builtin<'a> {
const I16_SIZE: u32 = std::mem::size_of::<i16>() as u32;
const I8_SIZE: u32 = std::mem::size_of::<i8>() as u32;
const I1_SIZE: u32 = std::mem::size_of::<bool>() as u32;
const USIZE_SIZE: u32 = std::mem::size_of::<usize>() as u32;
const DECIMAL_SIZE: u32 = std::mem::size_of::<i128>() as u32;
const F128_SIZE: u32 = 16;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
@ -1068,7 +1083,7 @@ impl<'a> Builtin<'a> {
Int16 => Builtin::I16_SIZE,
Int8 => Builtin::I8_SIZE,
Int1 => Builtin::I1_SIZE,
Usize => Builtin::USIZE_SIZE,
Usize => pointer_size,
Decimal => Builtin::DECIMAL_SIZE,
Float128 => Builtin::F128_SIZE,
Float64 => Builtin::F64_SIZE,
@ -1095,7 +1110,7 @@ impl<'a> Builtin<'a> {
Int16 => align_of::<i16>() as u32,
Int8 => align_of::<i8>() as u32,
Int1 => align_of::<bool>() as u32,
Usize => align_of::<usize>() as u32,
Usize => pointer_size,
Decimal => align_of::<i128>() as u32,
Float128 => align_of::<i128>() as u32,
Float64 => align_of::<f64>() as u32,
@ -1182,6 +1197,7 @@ fn layout_from_flat_type<'a>(
let arena = env.arena;
let subs = env.subs;
let ptr_bytes = env.ptr_bytes;
match flat_type {
Apply(symbol, args) => {
@ -1273,7 +1289,7 @@ fn layout_from_flat_type<'a>(
}
}
Func(_, closure_var, _) => {
let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var)?;
let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?;
Ok(lambda_set.runtime_representation())
}
@ -1299,8 +1315,6 @@ fn layout_from_flat_type<'a>(
let mut pairs = Vec::from_iter_in(pairs_it, arena);
pairs.sort_by(|(label1, layout1), (label2, layout2)| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
@ -1320,14 +1334,14 @@ fn layout_from_flat_type<'a>(
TagUnion(tags, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
Ok(layout_from_tag_union(arena, tags, subs))
Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes))
}
FunctionOrTagUnion(tag_name, _, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
let tags = UnionTags::from_tag_name_index(tag_name);
Ok(layout_from_tag_union(arena, tags, subs))
Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes))
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
@ -1377,8 +1391,6 @@ fn layout_from_flat_type<'a>(
}
tag_layout.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
@ -1425,11 +1437,13 @@ pub fn sort_record_fields<'a>(
arena: &'a Bump,
var: Variable,
subs: &Subs,
ptr_bytes: u32,
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
let mut env = Env {
arena,
subs,
seen: Vec::new_in(arena),
ptr_bytes,
};
let (it, _) = gather_fields_unsorted_iter(subs, RecordFields::empty(), var);
@ -1445,6 +1459,8 @@ fn sort_record_fields_help<'a>(
env: &mut Env<'a, '_>,
fields_map: impl Iterator<Item = (Lowercase, RecordField<Variable>)>,
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
let ptr_bytes = env.ptr_bytes;
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.size_hint().0, env.arena);
@ -1468,8 +1484,6 @@ fn sort_record_fields_help<'a>(
|(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 {
Ok(layout1) | Err(layout1) => match res_layout2 {
Ok(layout2) | Err(layout2) => {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
@ -1605,6 +1619,7 @@ pub fn union_sorted_tags<'a>(
arena: &'a Bump,
var: Variable,
subs: &Subs,
ptr_bytes: u32,
) -> Result<UnionVariant<'a>, LayoutProblem> {
let var =
if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(var) {
@ -1617,7 +1632,7 @@ pub fn union_sorted_tags<'a>(
let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) {
Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => {
let opt_rec_var = get_recursion_var(subs, var);
union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs)
union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, ptr_bytes)
}
Err((_, Content::Error)) => return Err(LayoutProblem::Erroneous),
Err(other) => panic!("invalid content in tag union variable: {:?}", other),
@ -1651,6 +1666,7 @@ fn union_sorted_tags_help_new<'a>(
mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>,
opt_rec_var: Option<Variable>,
subs: &Subs,
ptr_bytes: u32,
) -> UnionVariant<'a> {
// sort up front; make sure the ordering stays intact!
tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
@ -1659,6 +1675,7 @@ fn union_sorted_tags_help_new<'a>(
arena,
subs,
seen: Vec::new_in(arena),
ptr_bytes,
};
match tags_vec.len() {
@ -1708,8 +1725,6 @@ fn union_sorted_tags_help_new<'a>(
}
layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
@ -1793,8 +1808,6 @@ fn union_sorted_tags_help_new<'a>(
}
arg_layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
@ -1867,6 +1880,7 @@ pub fn union_sorted_tags_help<'a>(
mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
opt_rec_var: Option<Variable>,
subs: &Subs,
ptr_bytes: u32,
) -> UnionVariant<'a> {
// sort up front; make sure the ordering stays intact!
tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
@ -1875,6 +1889,7 @@ pub fn union_sorted_tags_help<'a>(
arena,
subs,
seen: Vec::new_in(arena),
ptr_bytes,
};
match tags_vec.len() {
@ -1921,8 +1936,6 @@ pub fn union_sorted_tags_help<'a>(
}
layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
@ -2005,8 +2018,6 @@ pub fn union_sorted_tags_help<'a>(
}
arg_layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
@ -2091,7 +2102,12 @@ fn cheap_sort_tags<'a, 'b>(
tags_vec
}
fn layout_from_newtype<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> Layout<'a> {
fn layout_from_newtype<'a>(
arena: &'a Bump,
tags: UnionTags,
subs: &Subs,
ptr_bytes: u32,
) -> Layout<'a> {
debug_assert!(tags.is_newtype_wrapper(subs));
let slice_index = tags.variables().into_iter().next().unwrap();
@ -2109,6 +2125,7 @@ fn layout_from_newtype<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> Lay
arena,
subs,
seen: Vec::new_in(arena),
ptr_bytes,
};
match Layout::from_var(&mut env, var) {
@ -2128,11 +2145,16 @@ fn layout_from_newtype<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> Lay
}
}
fn layout_from_tag_union<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> Layout<'a> {
fn layout_from_tag_union<'a>(
arena: &'a Bump,
tags: UnionTags,
subs: &Subs,
ptr_bytes: u32,
) -> Layout<'a> {
use UnionVariant::*;
if tags.is_newtype_wrapper(subs) {
return layout_from_newtype(arena, tags, subs);
return layout_from_newtype(arena, tags, subs, ptr_bytes);
}
let tags_vec = cheap_sort_tags(arena, tags, subs);
@ -2148,7 +2170,7 @@ fn layout_from_tag_union<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> L
}
_ => {
let opt_rec_var = None;
let variant = union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs);
let variant = union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, ptr_bytes);
match variant {
Never => Layout::Union(UnionLayout::NonRecursive(&[])),

View File

@ -143,6 +143,8 @@ fn pattern_to_doc_help<'b>(
Bit(false) => alloc.text("False"),
Byte(b) => alloc.text(b.to_string()),
Float(f) => alloc.text(f.to_string()),
// TODO: Proper Dec.to_str
Decimal(d) => alloc.text(d.0.to_string()),
Str(s) => alloc.string(s.into()),
},
Ctor(union, tag_id, args) => {

View File

@ -92,14 +92,15 @@ mod test_reporting {
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mut layout_cache = LayoutCache::default();
let ptr_bytes = 8;
let mut layout_cache = LayoutCache::new(ptr_bytes);
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
ptr_bytes: 8,
ptr_bytes,
update_mode_counter: 0,
// call_specialization_counter=0 is reserved
call_specialization_counter: 1,

View File

@ -30,7 +30,7 @@ either = "1.6.1"
indoc = "0.3.3"
libc = "0.2"
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10"
target-lexicon = "0.12.2"
libloading = "0.6"
[dev-dependencies]

View File

@ -2398,7 +2398,7 @@ fn call_invalid_layout() {
}
#[test]
#[should_panic(expected = "assert failed!")]
#[should_panic(expected = "An expectation failed!")]
fn expect_fail() {
assert_evals_to!(
indoc!(

View File

@ -815,7 +815,7 @@ fn str_join_comma_single() {
#[test]
fn str_from_float() {
assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.140000"), RocStr);
assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
}
#[test]

View File

@ -169,7 +169,7 @@ pub fn helper<'a>(
}
let builder = context.create_builder();
let module = roc_gen_llvm::llvm::build::module_from_builtins(context, "app");
let module = roc_gen_llvm::llvm::build::module_from_builtins(context, "app", ptr_bytes);
let opt_level = if cfg!(debug_assertions) {
OptLevel::Normal

View File

@ -28,7 +28,7 @@ bumpalo = { version = "3.6.1", features = ["collections"] }
either = "1.6.1"
indoc = "0.3.3"
libc = "0.2"
target-lexicon = "0.10"
target-lexicon = "0.12.2"
libloading = "0.6"
[dev-dependencies]

View File

@ -961,19 +961,19 @@ fn define_integer_types(subs: &mut Subs) {
Variable::U8,
);
// integer_type(
// subs,
// Symbol::NUM_AT_NATURAL,
// Symbol::NUM_NATURAL,
// Symbol::NUM_NAT,
// Variable::AT_NATURAL,
// Variable::NATURAL,
// Variable::AT_INTEGER_NATURAL,
// Variable::INTEGER_NATURAL,
// Variable::AT_NUM_INTEGER_NATURAL,
// Variable::NUM_INTEGER_NATURAL,
// Variable::NAT,
// );
integer_type(
subs,
Symbol::NUM_AT_NATURAL,
Symbol::NUM_NATURAL,
Symbol::NUM_NAT,
Variable::AT_NATURAL,
Variable::NATURAL,
Variable::AT_INTEGER_NATURAL,
Variable::INTEGER_NATURAL,
Variable::AT_NUM_INTEGER_NATURAL,
Variable::NUM_INTEGER_NATURAL,
Variable::NAT,
);
}
impl Subs {

View File

@ -78,6 +78,14 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* [Utopia](https://utopia.app/) integrated design and development environment for React. Design and code update each other, in real time.
* [Paredit](https://calva.io/paredit/) structural clojure editing, navigation and selection. [Another overview](http://danmidwood.com/content/2014/11/21/animated-paredit.html)
### Project exploration
* Tree view or circle view (like Github Next) of project where exposed values and functions can be seen on hover.
#### Inspiration
* [Github Next](https://next.github.com/projects/repo-visualization) each file and folder is visualised as a circle: the circles color is the type of file, and the circles size represents the size of the file. Sidenote, a cool addition to this might be to use heatmap colors for the circles; circles for files that have had lots of commits could be more red, files with few commits would be blue.
### Voice Interaction Related
* We should label as many things as possible and expose jumps to those labels as shortkeys.
@ -231,6 +239,11 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
The API and documentation are meant to interface with humans.
* [DocC](https://developer.apple.com/videos/play/wwdc2021/10166/) neat documentation approach for swift.
## Tutorials
* Inclusion of step-by-step tutrials in Roc libraries, platforms or business specific code.
* Having to set up your own website for a tutorial can be a lot of work, making it easy to make quality tutorials would make for a more delightful experience.
## General Plugin Ideas
### Ideas

1
examples/.gitignore vendored
View File

@ -4,4 +4,3 @@ app
libhost.a
roc_app.ll
roc_app.bc
effect-example

View File

@ -1,4 +1,4 @@
interface AStar exposes [ findPath, Model, initialModel, cheapestOpen, takeStep, reconstructPath ] imports [Quicksort]
interface AStar exposes [ findPath, Model, initialModel, cheapestOpen, reconstructPath ] imports [Quicksort]
findPath = \costFn, moveFn, start, end ->
astar costFn moveFn end (initialModel start)
@ -111,24 +111,24 @@ astar = \costFn, moveFn, goal, model ->
astar costFn moveFn goal modelWithCosts
takeStep = \moveFn, _goal, model, current ->
modelPopped =
{ model &
openSet: Set.remove model.openSet current,
evaluated: Set.insert model.evaluated current,
}
neighbors = moveFn current
newNeighbors = Set.difference neighbors modelPopped.evaluated
modelWithNeighbors = { modelPopped & openSet: Set.union modelPopped.openSet newNeighbors }
# a lot goes wrong here
modelWithCosts =
Set.walk newNeighbors (\n, m -> updateCost current n m) modelWithNeighbors
modelWithCosts
# takeStep = \moveFn, _goal, model, current ->
# modelPopped =
# { model &
# openSet: Set.remove model.openSet current,
# evaluated: Set.insert model.evaluated current,
# }
#
# neighbors = moveFn current
#
# newNeighbors = Set.difference neighbors modelPopped.evaluated
#
# modelWithNeighbors = { modelPopped & openSet: Set.union modelPopped.openSet newNeighbors }
#
# # a lot goes wrong here
# modelWithCosts =
# Set.walk newNeighbors (\n, m -> updateCost current n m) modelWithNeighbors
#
# modelWithCosts

View File

@ -29,23 +29,33 @@ extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_1_Fx_size() i64;
extern fn roc__mainForHost_1_Fx_result_size() i64;
extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
_ = old_size;
_ = alignment;
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
_ = alignment;
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
@ -54,12 +64,9 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
const Unit = extern struct {};
pub export fn main() u8 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
pub fn main() u8 {
const size = @intCast(usize, roc__mainForHost_size());
const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable;
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output);
defer {
@ -71,17 +78,17 @@ pub export fn main() u8 {
roc__mainForHost_1_exposed(output);
const elements = @ptrCast([*]u64, @alignCast(8, output));
var flag = elements[0];
const flag = @ptrCast(*u64, @alignCast(@alignOf(u64), output)).*;
if (flag == 0) {
// all is well
const closure_data_pointer = @ptrCast([*]u8, output[8..size]);
const closure_data_pointer = @ptrCast([*]u8, output[@sizeOf(u64)..size]);
call_the_closure(closure_data_pointer);
} else {
const msg = @intToPtr([*:0]const u8, elements[1]);
const ptr = @ptrCast(*u32, output + @sizeOf(u64));
const msg = @intToPtr([*:0]const u8, ptr.*);
const stderr = std.io.getStdErr().writer();
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
return 0;
@ -92,6 +99,7 @@ pub export fn main() u8 {
const delta = to_seconds(ts2) - to_seconds(ts1);
const stderr = std.io.getStdErr().writer();
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;
return 0;
@ -103,7 +111,7 @@ fn to_seconds(tms: std.os.timespec) f64 {
fn call_the_closure(closure_data_pointer: [*]u8) void {
const size = roc__mainForHost_1_Fx_result_size();
const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable;
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output);
defer {
@ -135,7 +143,7 @@ pub export fn roc_fx_putInt(int: i64) i64 {
return 0;
}
pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
export fn roc_fx_putLine(rocPath: str.RocStr) callconv(.C) void {
const stdout = std.io.getStdOut().writer();
for (rocPath.asSlice()) |char| {
@ -143,8 +151,6 @@ pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
}
stdout.print("\n", .{}) catch unreachable;
return 0;
}
const GetInt = extern struct {

1
examples/cli/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
echo

18
examples/cli/Echo.roc Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env roc
app "echo"
packages { base: "platform" }
imports [ base.Task.{ Task, await }, base.Stdout, base.Stdin ]
provides [ main ] to base
main : Task {} *
main =
{} <- await (Stdout.line "What's your first name?")
firstName <- await Stdin.line
{} <- await (Stdout.line "What's your last name?")
lastName <- await Stdin.line
Stdout.line "Hi, \(firstName) \(lastName)!"

BIN
examples/cli/cli-example Executable file

Binary file not shown.

BIN
examples/cli/hello-world Executable file

Binary file not shown.

21
examples/cli/platform/Cargo.lock generated Normal file
View File

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

View File

@ -0,0 +1,15 @@
[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" }
libc = "0.2"
[workspace]

View File

@ -0,0 +1,14 @@
platform examples/cli
requires {}{ main : Task {} [] } # TODO FIXME
exposes []
packages {}
imports [ Task.{ Task } ]
provides [ mainForHost ]
effects fx.Effect
{
putLine : Str -> Effect {},
getLine : Effect Str
}
mainForHost : Task {} [] as Fx
mainForHost = main

View File

@ -0,0 +1,6 @@
interface Stdin
exposes [ line ]
imports [ fx.Effect, Task ]
line : Task.Task Str *
line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice

View File

@ -0,0 +1,9 @@
interface Stdout
exposes [ line ]
imports [ fx.Effect, Task.{ Task } ]
# line : Str -> Task.Task {} *
# line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
line : Str -> Task {} *
line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {})

View File

@ -0,0 +1,44 @@
interface Task
exposes [ Task, succeed, fail, await, map, onFail, attempt ]
imports [ fx.Effect ]
Task ok err : Effect.Effect (Result ok err)
succeed : val -> Task val *
succeed = \val ->
Effect.always (Ok val)
fail : err -> Task * err
fail = \val ->
Effect.always (Err val)
attempt : Task a b, (Result a b -> Task c d) -> Task c d
attempt = \effect, transform ->
Effect.after effect \result ->
when result is
Ok ok -> transform (Ok ok)
Err err -> transform (Err err)
await : Task a err, (a -> Task b err) -> Task b err
await = \effect, transform ->
Effect.after effect \result ->
when result is
Ok a -> transform a
Err err -> Task.fail err
onFail : Task ok a, (a -> Task ok b) -> Task ok b
onFail = \effect, transform ->
Effect.after effect \result ->
when result is
Ok a -> Task.succeed a
Err err -> transform err
map : Task a err, (a -> b) -> Task b err
map = \effect, transform ->
Effect.after effect \result ->
when result is
Ok a -> Task.succeed (transform a)
Err err -> Task.fail err

View File

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

View File

@ -0,0 +1,139 @@
#![allow(non_snake_case)]
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::MaybeUninit;
use libc;
use roc_std::{RocCallResult, RocStr};
use std::ffi::CStr;
use std::os::raw::c_char;
extern "C" {
#[link_name = "roc__mainForHost_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "roc__mainForHost_size"]
fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_1_Fx_caller"]
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[allow(dead_code)]
#[link_name = "roc__mainForHost_1_Fx_size"]
fn size_Fx() -> i64;
#[link_name = "roc__mainForHost_1_Fx_result_size"]
fn size_Fx_result() -> i64;
}
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::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 {
libc::realloc(c_ptr, new_size)
}
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub fn rust_main() -> isize {
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
unsafe {
// TODO allocate on the stack if it's under a certain size
let buffer = std::alloc::alloc(layout);
roc_main(buffer);
let output = buffer as *mut RocCallResult<()>;
match (&*output).into() {
Ok(()) => {
let closure_data_ptr = buffer.offset(8);
let result = call_the_closure(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);
}
}
};
// Exit code
0
}
unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
let size = size_Fx_result() as usize;
let layout = Layout::array::<u8>(size).unwrap();
let buffer = std::alloc::alloc(layout) as *mut u8;
call_Fx(
// This flags pointer will never get dereferenced
MaybeUninit::uninit().as_ptr(),
closure_data_ptr as *const u8,
buffer as *mut u8,
);
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(_) => {
std::alloc::dealloc(buffer, layout);
0
}
Err(e) => panic!("failed with {}", e),
}
}
#[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(line1.as_bytes())
}
#[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);
// don't mess with the refcount!
core::mem::forget(line);
()
}

View File

@ -19,23 +19,32 @@ comptime {
}
}
extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, old_size: usize, new_size: usize, alignment: u32) callconv(.C) ?*c_void {
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
_ = old_size;
_ = alignment;
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
_ = alignment;
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
@ -47,11 +56,11 @@ const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocCallResult) void;
const RocCallResult = extern struct { flag: usize, content: RocStr };
const RocCallResult = extern struct { flag: u64, content: RocStr };
const Unit = extern struct {};
pub export fn main() i32 {
pub fn main() u8 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();

View File

@ -584,6 +584,10 @@ impl RocStr {
let raw_ptr = Self::get_element_ptr(raw_ptr as *mut u8);
// write the refcount
let refcount_ptr = raw_ptr as *mut isize;
*(refcount_ptr.offset(-1)) = isize::MIN;
{
// NOTE: using a memcpy here causes weird issues
let target_ptr = raw_ptr as *mut u8;
@ -832,11 +836,9 @@ impl RocDec {
}
};
let after_point = match parts.next() {
Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => answer,
_ => {
return None;
}
let opt_after_point = match parts.next() {
Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => Some(answer),
_ => None,
};
// There should have only been one "." in the string!
@ -845,22 +847,27 @@ impl RocDec {
}
// Calculate the low digits - the ones after the decimal point.
let lo = match after_point.parse::<i128>() {
Ok(answer) => {
// Translate e.g. the 1 from 0.1 into 10000000000000000000
// by "restoring" the elided trailing zeroes to the number!
let trailing_zeroes = Self::DECIMAL_PLACES as usize - after_point.len();
let lo = answer * 10i128.pow(trailing_zeroes as u32);
let lo = match opt_after_point {
Some(after_point) => {
match after_point.parse::<i128>() {
Ok(answer) => {
// Translate e.g. the 1 from 0.1 into 10000000000000000000
// by "restoring" the elided trailing zeroes to the number!
let trailing_zeroes = Self::DECIMAL_PLACES as usize - after_point.len();
let lo = answer * 10i128.pow(trailing_zeroes as u32);
if !before_point.starts_with('-') {
lo
} else {
-lo
if !before_point.starts_with('-') {
lo
} else {
-lo
}
}
Err(_) => {
return None;
}
}
}
Err(_) => {
return None;
}
None => 0,
};
// Calculate the high digits - the ones before the decimal point.