Merge branch 'trunk' into fix-nix-on-mac-os

This commit is contained in:
Ju Liu 2021-08-30 22:54:25 +01:00
commit 7290139512
91 changed files with 3183 additions and 1430 deletions

View File

@ -1,12 +1,10 @@
# Building the Roc compiler from source
## Installing LLVM, Python, Zig, valgrind, libunwind, and libc++-dev
## Installing LLVM, Zig, valgrind, and Python 2.7
To build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed)
* `libc++-dev` and `libc++abi-dev`
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* [Zig](https://ziglang.org/), see below for version
* LLVM, see below for version
@ -18,11 +16,6 @@ Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specifi
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be done with `sudo apt-get install libunwind-dev`).
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev libc++abi-dev`.)
### libcxb libraries
You may see an error like this during builds:
@ -182,7 +175,7 @@ Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys`
on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMacKenzie) (thank you, Ian!), here's what worked for me:
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work tool; the Build Tools are just the CLI tools, which is all I wanted)
1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools"
1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools" [note: as of September 2021 this should no longer be necessary - the next time anyone tries this, please try it without this step and make a PR to delete this step if it's no longer needed!]
1. I launched the "x64 Native Tools Command Prompt for Visual Studio 2019" application (note: not the similarly-named "x86" one!)
1. Make sure [Python 2.7](https://www.python.org/) and [CMake 3.17](http://cmake.org/) are installed on your system.
1. I followed most of the steps under LLVM's [building from source instructions](https://github.com/llvm/llvm-project#getting-the-source-code-and-building-llvm) up to the `cmake -G ...` command, which didn't work for me. Instead, at that point I did the following step.

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",
@ -3364,6 +3365,7 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_solve",
"roc_std",
"roc_types",
"roc_unify",
"ven_ena",
@ -3916,9 +3918,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

@ -8,7 +8,7 @@ install-other-libs:
FROM +prep-debian
RUN apt -y install wget git
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libc++-dev libc++abi-dev g++ libunwind-dev pkg-config libx11-dev zlib1g-dev
RUN apt -y install libunwind-dev pkg-config libx11-dev zlib1g-dev
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
@ -48,49 +48,18 @@ install-zig-llvm-valgrind-clippy-rustfmt:
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ENV SCCACHE_DIR=/earthbuild/sccache_dir
ENV CARGO_INCREMENTAL=0 # no need to recompile package when using new function
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo install cargo-chef
deps-image:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
SAVE IMAGE roc-deps:latest
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
# If you edit this, make sure to update copy-dirs-and-cache below.
COPY --dir cli compiler docs editor roc_std vendor examples Cargo.toml Cargo.lock ./
copy-dirs-and-cache:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY +save-cache/target ./target
COPY +save-cache/cargo_home $CARGO_HOME
# This needs to be kept in sync with copy-dirs above.
# The reason this is at the end is to maximize caching.
# Lines above this should be cached even if the code changes.
COPY --dir cli compiler docs editor roc_std vendor examples Cargo.toml Cargo.lock ./
prepare-cache:
FROM +copy-dirs
RUN cargo chef prepare
SAVE ARTIFACT recipe.json
save-cache:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY +prepare-cache/recipe.json ./
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo chef cook && sccache --show-stats # for clippy
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo chef cook --release --tests && sccache --show-stats
SAVE ARTIFACT target
SAVE ARTIFACT $CARGO_HOME cargo_home
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode && ./run-tests.sh
check-clippy:
FROM +copy-dirs-and-cache
FROM +copy-dirs
RUN cargo clippy -V
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings
@ -106,7 +75,7 @@ check-typos:
RUN typos
test-rust:
FROM +copy-dirs-and-cache
FROM +copy-dirs
ENV RUST_BACKTRACE=1
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release && sccache --show-stats
@ -132,7 +101,7 @@ test-all:
# compile everything needed for benchmarks and output a self-contained folder
prep-bench-folder:
FROM +copy-dirs-and-cache
FROM +copy-dirs
ARG BENCH_SUFFIX=branch
RUN cargo criterion -V
RUN --mount=type=cache,target=$SCCACHE_DIR cd cli && cargo criterion --no-run

View File

@ -59,7 +59,7 @@ esac
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}"
apt-get update
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev libc6-dbg libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc6-dbg libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
tar -xf valgrind-3.16.1.tar.bz2

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

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

View File

@ -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

@ -9,12 +9,12 @@ const RocList = @import("list.zig").RocList;
const INITIAL_SEED = 0xc70f6907;
const InPlace = packed enum(u8) {
const InPlace = enum(u8) {
InPlace,
Clone,
};
const Slot = packed enum(u8) {
const Slot = enum(u8) {
Empty,
Filled,
PreviouslyFilled,
@ -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 = packed 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;
}
}
};
@ -359,7 +353,7 @@ pub const RocDict = extern struct {
// hash the key, and modulo by the maximum size
// (so we get an in-bounds index)
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + (hash % current_level_size);
const index = capacityOfLevel(current_level - 1) + @intCast(usize, (hash % current_level_size));
switch (self.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => {
@ -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 {
@ -426,7 +420,7 @@ pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width:
}
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + (hash % current_level_size);
const index = capacityOfLevel(current_level - 1) + @intCast(usize, (hash % current_level_size));
assert(index < result.capacity());
switch (result.getSlot(index, key_width, value_width)) {

View File

@ -180,8 +180,8 @@ pub const Wyhash = struct {
}
pub fn final(self: *Wyhash) u64 {
const seed = self.state.seed;
const rem_len = @intCast(u5, self.buf_len);
// const seed = self.state.seed;
// const rem_len = @intCast(u5, self.buf_len);
const rem_key = self.buf[0..self.buf_len];
return self.state.final(rem_key);

View File

@ -1,5 +1,4 @@
const utils = @import("utils.zig");
const roc_mem = @import("mem.zig");
const RocList = @import("list.zig").RocList;
const std = @import("std");
const mem = std.mem;
@ -10,7 +9,7 @@ const expectEqual = testing.expectEqual;
const expectError = testing.expectError;
const expect = testing.expect;
const InPlace = packed enum(u8) {
const InPlace = enum(u8) {
InPlace,
Clone,
};
@ -52,7 +51,7 @@ pub const RocStr = extern struct {
return result;
}
pub fn initBig(in_place: InPlace, number_of_chars: u64) RocStr {
pub fn initBig(_: InPlace, number_of_chars: usize) RocStr {
const first_element = utils.allocateWithRefcount(number_of_chars, @sizeOf(usize));
return RocStr{
@ -222,7 +221,7 @@ pub const RocStr = extern struct {
// null-terminated strings. Otherwise, we need to allocate and copy a new
// null-terminated string, which has a much higher performance cost!
fn isNullTerminated(self: RocStr) bool {
const len = self.len();
const length = self.len();
const longest_small_str = @sizeOf(RocStr) - 1;
// NOTE: We want to compare length here, *NOT* check for is_small_str!
@ -231,7 +230,7 @@ pub const RocStr = extern struct {
//
// (The other branch dereferences the bytes pointer, which is not safe
// to do for the empty string.)
if (len <= longest_small_str) {
if (length <= longest_small_str) {
// If we're a small string, then usually the next byte after the
// end of the string will be zero. (Small strings set all their
// unused bytes to 0, so that comparison for equality can be fast.)
@ -242,7 +241,7 @@ pub const RocStr = extern struct {
// Also, if we are exactly a maximum-length small string,
// then the next byte is off the end of the struct;
// in that case, we are also not null-terminated!
return len != 0 and len != longest_small_str;
return length != 0 and length != longest_small_str;
} else {
// This is a big string, and it's not empty, so we can safely
// dereference the pointer.
@ -253,8 +252,8 @@ pub const RocStr = extern struct {
//
// If we have excess capacity, then we can safely read the next
// byte after the end of the string. Maybe it happens to be zero!
if (capacity_or_refcount > @intCast(isize, len)) {
return self.str_bytes[len] == 0;
if (capacity_or_refcount > @intCast(isize, length)) {
return self.str_bytes[length] == 0;
} else {
// This string was refcounted or immortal; we can't safely read
// the next byte, so assume the string is not null-terminated.
@ -267,10 +266,10 @@ pub const RocStr = extern struct {
// Returns 0 for refcounted stirngs and immortal strings.
// Returns the stored capacity value for all other strings.
pub fn capacity(self: RocStr) usize {
const len = self.len();
const length = self.len();
const longest_small_str = @sizeOf(RocStr) - 1;
if (len <= longest_small_str) {
if (length <= longest_small_str) {
// Note that although empty strings technically have the full
// capacity of a small string available, they aren't marked as small
// strings, so if you want to make use of that capacity, you need
@ -314,9 +313,17 @@ 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));
// return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
if (self.isSmallStr() or self.isEmpty()) {
const as_int = @ptrToInt(&self);
const as_ptr = @intToPtr([*]u8, as_int);
return as_ptr;
} else {
return @ptrCast([*]u8, self.str_bytes);
}
}
// Given a pointer to some bytes, write the first (len) bytes of this
@ -408,7 +415,7 @@ pub fn strFromIntC(int: i64) callconv(.C) RocStr {
fn strFromIntHelp(comptime T: type, int: T) RocStr {
// determine maximum size for this T
comptime const size = comptime blk: {
const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters
var buf: [40]u8 = undefined;
var result = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
@ -423,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
@ -785,8 +788,6 @@ pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
return count;
}
fn rocStrFromLiteral(bytes_arr: *const []u8) RocStr {}
test "countGraphemeClusters: empty string" {
const count = countGraphemeClusters(RocStr.empty());
try expectEqual(count, 0);
@ -867,7 +868,6 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
// Str.startsWithCodePt
pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool {
const bytes_len = string.len();
const bytes_ptr = string.asU8ptr();
var buffer: [4]u8 = undefined;
@ -1266,7 +1266,7 @@ pub fn numberOfNextCodepointBytes(ptr: [*]u8, len: usize, index: usize) Utf8Deco
// Return types for validateUtf8Bytes
// Values must be in alphabetical order. That is, lowest values are the first alphabetically.
pub const Utf8ByteProblem = packed enum(u8) {
pub const Utf8ByteProblem = enum(u8) {
CodepointTooLarge = 0,
EncodesSurrogateHalf = 1,
ExpectedContinuation = 2,

View File

@ -45,7 +45,10 @@ fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void {
std.testing.allocator.destroy(ptr);
}
fn testing_roc_panic(c_ptr: *c_void, _: u32) callconv(.C) void {
fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = c_ptr;
_ = tag_id;
@panic("Roc paniced");
}
@ -69,7 +72,9 @@ pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
// indirection because otherwise zig creats an alias to the panic function which our LLVM code
// does not know how to deal with
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
const cstr = @ptrCast([*:0]u8, c_ptr);
_ = c_ptr;
_ = alignment;
// const cstr = @ptrCast([*:0]u8, c_ptr);
// const stderr = std.io.getStdErr().writer();
// stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable;
@ -110,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);
@ -123,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) {
@ -226,7 +245,7 @@ pub const RocResult = extern struct {
}
};
pub const Ordering = packed enum(u8) {
pub const Ordering = enum(u8) {
EQ = 0,
GT = 1,
LT = 2,

View File

@ -24,39 +24,55 @@ fn main() {
return;
}
let big_sur_path = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib";
let use_build_script = Path::new(big_sur_path).exists();
// "." is relative to where "build.rs" is
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
let src_obj_path = bitcode_path.join("builtins.o");
let src_obj_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");
if use_build_script {
println!("Compiling zig object & ir to: {} and {}", src_obj, dest_ir);
run_command_with_no_args(&bitcode_path, "./build.sh");
} else {
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
let dest_ir_path = bitcode_path.join("builtins-64bit.ll");
let dest_ir_64bit = dest_ir_path.to_str().expect("Invalid dest ir path");
println!("Compiling ir to: {}", dest_ir);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
}
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
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;
@ -92,25 +108,6 @@ where
}
}
fn run_command_with_no_args<P: AsRef<Path>>(path: P, command_str: &str) {
let output_result = Command::new(OsStr::new(&command_str))
.current_dir(path)
.output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
panic!("{} failed: {}", command_str, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {

View File

@ -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,45 +78,32 @@ 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(())
}
Stmt::Invoke {
symbol,
layout,
call,
pass,
fail: _,
exception_id: _,
} => {
// for now, treat invoke as a normal call
self.build_expr(symbol, &Expr::Call(call.clone()), layout)?;
self.free_symbols(stmt);
self.build_stmt(pass)
}
Stmt::Switch {
cond_symbol,
cond_layout,
@ -231,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)),
}
}
@ -390,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>) {
@ -499,20 +516,6 @@ where
self.scan_ast(following);
}
Stmt::Invoke {
symbol,
layout: _,
call,
pass,
fail: _,
exception_id: _,
} => {
// for now, treat invoke as a normal call
self.set_last_seen(*symbol, stmt);
self.scan_ast_call(call, stmt);
self.scan_ast(pass);
}
Stmt::Switch {
cond_symbol,
branches,
@ -528,7 +531,6 @@ where
Stmt::Ret(sym) => {
self.set_last_seen(*sym, stmt);
}
Stmt::Resume(_exception_id) => {}
Stmt::Refcounting(modify, following) => {
let sym = modify.get_symbol();

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),
}
}
@ -2198,7 +2215,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
parent: FunctionValue<'ctx>,
stmt: &roc_mono::ir::Stmt<'a>,
) -> BasicValueEnum<'ctx> {
use roc_mono::ir::Expr;
use roc_mono::ir::Stmt::*;
match stmt {
@ -2259,48 +2275,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
value
}
Invoke {
symbol,
call,
layout,
pass,
fail: roc_mono::ir::Stmt::Resume(_),
exception_id: _,
} => {
// when the fail case is just Rethrow, there is no cleanup work to do
// so we can just treat this invoke as a normal call
let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, &stmt)
}
Invoke {
call, layout: _, ..
} => match call.call_type {
CallType::ByName { .. } => {
unreachable!("we should not end up here")
}
CallType::Foreign {
ref foreign_symbol,
ref ret_layout,
} => build_foreign_symbol(env, scope, foreign_symbol, call.arguments, ret_layout),
CallType::LowLevel { .. } => {
unreachable!("lowlevel itself never throws exceptions")
}
CallType::HigherOrderLowLevel { .. } => {
unreachable!("lowlevel itself never throws exceptions")
}
},
Resume(exception_id) => {
let exception_object = scope.get(&exception_id.into_inner()).unwrap().1;
env.builder.build_resume(exception_object);
env.ptr_int().const_zero().into()
}
Switch {
branches,
default_branch,
@ -3974,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,
}
}
@ -4472,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>,
@ -4482,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::*;
@ -5200,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
@ -5213,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>(
@ -6117,7 +6193,6 @@ fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) {
)
.into_pointer_value();
// cxa_throw_exception(env, info);
env.call_panic(cast, PanicTagId::NullTerminatedString);
builder.build_unreachable();

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

@ -289,30 +289,6 @@ fn stmt_spec<'a>(
Ok(result)
}
Invoke {
symbol,
call,
layout: call_layout,
pass,
fail,
exception_id: _,
} => {
// a call that might throw an exception
let value_id = call_spec(builder, env, block, call_layout, call)?;
let pass_block = builder.add_block();
env.symbols.insert(*symbol, value_id);
let pass_value_id = stmt_spec(builder, env, pass_block, layout, pass)?;
env.symbols.remove(symbol);
let pass_block_expr = BlockExpr(pass_block, pass_value_id);
let fail_block = builder.add_block();
let fail_value_id = stmt_spec(builder, env, fail_block, layout, fail)?;
let fail_block_expr = BlockExpr(fail_block, fail_value_id);
builder.add_choice(block, &[pass_block_expr, fail_block_expr])
}
Switch {
cond_symbol: _,
cond_layout: _,
@ -420,7 +396,7 @@ fn stmt_spec<'a>(
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
Resume(_) | RuntimeError(_) => {
RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?;
builder.add_terminate(block, type_id)
@ -1193,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

@ -46,7 +46,14 @@ pub fn infer_borrow<'a>(
// component (in top-sorted order, from primitives (std-lib) to main)
let successor_map = &make_successor_mapping(arena, procs);
let successors = move |key: &Symbol| successor_map[key].iter().copied();
let successors = move |key: &Symbol| {
let slice = match successor_map.get(key) {
None => &[] as &[_],
Some(s) => s.as_slice(),
};
slice.iter().copied()
};
let mut symbols = Vec::with_capacity_in(procs.len(), arena);
symbols.extend(procs.keys().map(|x| x.0));
@ -217,7 +224,10 @@ impl<'a> DeclarationToIndex<'a> {
}
}
}
unreachable!("symbol/layout combo must be in DeclarationToIndex")
unreachable!(
"symbol/layout {:?} {:?} combo must be in DeclarationToIndex",
needle_symbol, needle_layout
)
}
}
@ -359,10 +369,6 @@ impl<'a> ParamMap<'a> {
Let(_, _, _, cont) => {
stack.push(cont);
}
Invoke { pass, fail, .. } => {
stack.push(pass);
stack.push(fail);
}
Switch {
branches,
default_branch,
@ -373,7 +379,7 @@ impl<'a> ParamMap<'a> {
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | RuntimeError(_) => {
// these are terminal, do nothing
}
}
@ -878,23 +884,6 @@ impl<'a> BorrowInfState<'a> {
}
}
Invoke {
symbol,
call,
layout: _,
pass,
fail,
exception_id: _,
} => {
self.collect_stmt(param_map, pass);
self.collect_stmt(param_map, fail);
self.collect_call(param_map, *symbol, call);
// TODO how to preserve the tail call of an invoke?
// self.preserve_tail_call(*x, v, b);
}
Jump(j, ys) => {
let ps = param_map.get_join_point(*j);
@ -916,7 +905,7 @@ impl<'a> BorrowInfState<'a> {
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | RuntimeError(_) | Resume(_) => {
Ret(_) | RuntimeError(_) => {
// these are terminal, do nothing
}
}
@ -1096,13 +1085,6 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>)
}
stack.push(cont);
}
Invoke {
call, pass, fail, ..
} => {
call_info_call(call, info);
stack.push(pass);
stack.push(fail);
}
Switch {
branches,
default_branch,
@ -1113,7 +1095,7 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>)
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | RuntimeError(_) => {
// these are terminal, do nothing
}
}

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

@ -563,29 +563,6 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
expand_and_cancel(env, cont)
}
Invoke {
symbol,
call,
layout,
pass,
fail,
exception_id,
} => {
let pass = expand_and_cancel(env, pass);
let fail = expand_and_cancel(env, fail);
let stmt = Invoke {
symbol: *symbol,
call: call.clone(),
layout: *layout,
pass,
fail,
exception_id: *exception_id,
};
env.arena.alloc(stmt)
}
Join {
id,
parameters,
@ -605,7 +582,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
env.arena.alloc(stmt)
}
Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
}
};

View File

@ -32,26 +32,10 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>)
stack.push(cont);
}
Invoke {
symbol,
call,
pass,
fail,
..
} => {
occurring_variables_call(call, &mut result);
result.insert(*symbol);
bound_variables.insert(*symbol);
stack.push(pass);
stack.push(fail);
}
Ret(symbol) => {
result.insert(*symbol);
}
Resume(_) => {}
Refcounting(modify, cont) => {
let symbol = modify.get_symbol();
result.insert(symbol);
@ -238,11 +222,6 @@ fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool {
}
}
fn consume_call(_: &VarMap, _: &crate::ir::Call<'_>) -> bool {
// variables bound by a call (or invoke) must always be consumed
true
}
impl<'a> Context<'a> {
pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self {
let mut vars = MutMap::default();
@ -814,22 +793,6 @@ impl<'a> Context<'a> {
(new_b, live_vars)
}
fn update_var_info_invoke(
&self,
symbol: Symbol,
layout: &Layout<'a>,
call: &crate::ir::Call<'a>,
) -> Self {
// is this value a constant?
// TODO do function pointers also fall into this category?
let persistent = call.arguments.is_empty();
// must this value be consumed?
let consume = consume_call(&self.vars, call);
self.update_var_info_help(symbol, layout, persistent, consume, false)
}
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
// is this value a constant?
// TODO do function pointers also fall into this category?
@ -965,82 +928,6 @@ impl<'a> Context<'a> {
ctx.visit_variable_declaration(*symbol, expr.clone(), *layout, b, &b_live_vars)
}
Invoke {
symbol,
call,
pass,
fail,
layout,
exception_id,
} => {
// live vars of the whole expression
let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
let fail = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(fail);
ctx.add_dec_for_alt(&invoke_live_vars, &alt_live_vars, b)
};
let pass = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let ctx = ctx.update_var_info_invoke(*symbol, layout, call);
let (b, alt_live_vars) = ctx.visit_stmt(pass);
ctx.add_dec_for_alt(&invoke_live_vars, &alt_live_vars, b)
};
let invoke = Invoke {
symbol: *symbol,
call: call.clone(),
pass,
fail,
layout: *layout,
exception_id: *exception_id,
};
let cont = self.arena.alloc(invoke);
use crate::ir::CallType;
let stmt = match &call.call_type {
CallType::LowLevel { op, .. } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &invoke_live_vars)
}
CallType::HigherOrderLowLevel { .. } => {
todo!("copy the code for normal calls over to here");
}
CallType::Foreign { .. } => {
let ps = crate::borrow::foreign_borrow_signature(
self.arena,
call.arguments.len(),
);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &invoke_live_vars)
}
CallType::ByName {
name,
ret_layout,
arg_layouts,
..
} => {
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
// get the borrow signature
let ps = self
.param_map
.get_symbol(*name, top_level)
.expect("function is defined");
self.add_dec_after_application(call.arguments, ps, cont, &invoke_live_vars)
}
};
(stmt, invoke_live_vars)
}
Join {
id: j,
parameters: _,
@ -1086,8 +973,6 @@ impl<'a> Context<'a> {
}
}
Resume(_) => (stmt, MutSet::default()),
Jump(j, xs) => {
let empty = MutSet::default();
let j_live_vars = match self.jp_live_vars.get(j) {
@ -1176,25 +1061,7 @@ pub fn collect_stmt(
vars
}
Invoke {
symbol,
call,
pass,
fail,
..
} => {
vars = collect_stmt(pass, jp_live_vars, vars);
vars = collect_stmt(fail, jp_live_vars, vars);
vars.remove(symbol);
let mut result = MutSet::default();
occurring_variables_call(call, &mut result);
vars.extend(result);
vars
}
Ret(symbol) => {
vars.insert(*symbol);
vars
@ -1252,8 +1119,6 @@ pub fn collect_stmt(
vars
}
Resume(_) => vars,
RuntimeError(_) => vars,
}
}

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;
@ -694,10 +695,8 @@ impl<'a> Procs<'a> {
layout: ProcLayout<'a>,
layout_cache: &mut LayoutCache<'a>,
) {
let tuple = (name, layout);
// If we've already specialized this one, no further work is needed.
if self.specialized.contains_key(&tuple) {
if self.specialized.contains_key(&(name, layout)) {
return;
}
@ -707,15 +706,12 @@ impl<'a> Procs<'a> {
return;
}
// We're done with that tuple, so move layout back out to avoid cloning it.
let (name, layout) = tuple;
let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var);
// This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations {
Some(pending_specializations) => {
let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var);
// register the pending specialization, so this gets code genned later
if self.module_thunks.contains(&name) {
debug_assert!(layout.arguments.is_empty());
@ -736,7 +732,26 @@ impl<'a> Procs<'a> {
// (We had a bug around this before this system existed!)
self.specialized.insert((symbol, layout), InProgress);
match specialize(env, self, symbol, layout_cache, pending, partial_proc) {
// See https://github.com/rtfeldman/roc/issues/1600
//
// The annotation variable is the generic/lifted/top-level annotation.
// It is connected to the variables of the function's body
//
// fn_var is the variable representing the type that we actually need for the
// function right here.
//
// For some reason, it matters that we unify with the original variable. Extracting
// that variable into a SolvedType and then introducing it again severs some
// connection that turns out to be important
match specialize_variable(
env,
self,
symbol,
layout_cache,
fn_var,
Default::default(),
partial_proc,
) {
Ok((proc, _ignore_layout)) => {
// the `layout` is a function pointer, while `_ignore_layout` can be a
// closure. We only specialize functions, storing this value with a closure
@ -885,17 +900,6 @@ pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
#[derive(Clone, Debug, PartialEq)]
pub enum Stmt<'a> {
Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>),
Invoke {
symbol: Symbol,
call: Call<'a>,
layout: Layout<'a>,
pass: &'a Stmt<'a>,
fail: &'a Stmt<'a>,
exception_id: ExceptionId,
},
/// after cleanup, rethrow the exception object (stored in the exception id)
/// so it bubbles up
Resume(ExceptionId),
Switch {
/// This *must* stand for an integer, because Switch potentially compiles to a jump table.
cond_symbol: Symbol,
@ -1021,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.
@ -1202,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)),
@ -1379,45 +1386,11 @@ impl<'a> Stmt<'a> {
.append(alloc.hardline())
.append(cont.to_doc(alloc)),
Invoke {
symbol,
call,
pass,
fail: Stmt::Resume(_),
..
} => alloc
.text("let ")
.append(symbol_to_doc(alloc, *symbol))
.append(" = ")
.append(call.to_doc(alloc))
.append(";")
.append(alloc.hardline())
.append(pass.to_doc(alloc)),
Invoke {
symbol,
call,
pass,
fail,
..
} => alloc
.text("invoke ")
.append(symbol_to_doc(alloc, *symbol))
.append(" = ")
.append(call.to_doc(alloc))
.append(" catch")
.append(alloc.hardline())
.append(fail.to_doc(alloc).indent(4))
.append(alloc.hardline())
.append(pass.to_doc(alloc)),
Ret(symbol) => alloc
.text("ret ")
.append(symbol_to_doc(alloc, *symbol))
.append(";"),
Resume(_) => alloc.text("unreachable;"),
Switch {
cond_symbol,
branches,
@ -1733,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)
@ -2493,13 +2466,57 @@ fn specialize_solved_type<'a>(
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
specialize_variable_help(
env,
procs,
proc_name,
layout_cache,
|env| introduce_solved_type_to_subs(env, &solved_type),
host_exposed_aliases,
partial_proc,
)
}
fn specialize_variable<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
proc_name: Symbol,
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
specialize_variable_help(
env,
procs,
proc_name,
layout_cache,
|_| fn_var,
host_exposed_aliases,
partial_proc,
)
}
fn specialize_variable_help<'a, F>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
proc_name: Symbol,
layout_cache: &mut LayoutCache<'a>,
fn_var_thunk: F,
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>>
where
F: FnOnce(&mut Env<'a, '_>) -> Variable,
{
// add the specializations that other modules require of us
use roc_solve::solve::instantiate_rigids;
let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot();
let fn_var = introduce_solved_type_to_subs(env, &solved_type);
// important: evaluate after the snapshot has been created!
let fn_var = fn_var_thunk(env);
// for debugging only
let raw = layout_cache
@ -2725,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,
),
@ -2743,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"),
}
}
@ -2768,32 +2791,41 @@ pub fn with_hole<'a>(
hole,
),
Num(var, num) => match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) {
IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Float(num as f64)),
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,
),
},
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,
Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)),
hole,
),
IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Float(num as f64)),
Layout::Builtin(float_precision_to_builtin(precision)),
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, _) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure {
@ -3092,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);
@ -3427,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);
@ -3569,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);
@ -4173,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(_)) => {
@ -4509,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))));
@ -4613,15 +4649,12 @@ pub fn from_can<'a>(
arguments,
};
let exception_id = ExceptionId(env.unique_symbol());
let rest = Stmt::Invoke {
symbol: env.unique_symbol(),
call,
layout: bool_layout,
pass: env.arena.alloc(rest),
fail: env.arena.alloc(Stmt::Resume(exception_id)),
exception_id,
};
let rest = Stmt::Let(
env.unique_symbol(),
Expr::Call(call),
bool_layout,
env.arena.alloc(rest),
);
with_hole(
env,
@ -4732,14 +4765,16 @@ pub fn from_can<'a>(
);
CapturedSymbols::None
}
Err(e) => {
debug_assert!(
captured_symbols.is_empty(),
"{:?}, {:?}",
&captured_symbols,
e
);
CapturedSymbols::None
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
let mut temp =
Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
};
@ -5205,35 +5240,6 @@ fn substitute_in_stmt_help<'a>(
None
}
}
Invoke {
symbol,
call,
layout,
pass,
fail,
exception_id,
} => {
let opt_call = substitute_in_call(arena, call, subs);
let opt_pass = substitute_in_stmt_help(arena, pass, subs);
let opt_fail = substitute_in_stmt_help(arena, fail, subs);
if opt_pass.is_some() || opt_fail.is_some() | opt_call.is_some() {
let pass = opt_pass.unwrap_or(pass);
let fail = opt_fail.unwrap_or_else(|| *fail);
let call = opt_call.unwrap_or_else(|| call.clone());
Some(arena.alloc(Invoke {
symbol: *symbol,
call,
layout: *layout,
pass,
fail,
exception_id: *exception_id,
}))
} else {
None
}
}
Join {
id,
parameters,
@ -5348,8 +5354,6 @@ fn substitute_in_stmt_help<'a>(
}
}
Resume(_) => None,
RuntimeError(_) => None,
}
}
@ -5588,6 +5592,7 @@ fn store_pattern_help<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
@ -5722,6 +5727,7 @@ fn store_tag_pattern<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
@ -5797,6 +5803,7 @@ fn store_newtype_pattern<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
@ -5872,6 +5879,7 @@ fn store_record_destruct<'a>(
}
IntLiteral(_)
| FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
@ -6228,18 +6236,6 @@ fn add_needed_external<'a>(
existing.insert(name, solved_type);
}
/// Symbol that links an Invoke with a Rethrow
/// we'll assign the exception object to this symbol
/// so we can later rethrow the exception
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct ExceptionId(Symbol);
impl ExceptionId {
pub fn into_inner(self) -> Symbol {
self.0
}
}
fn build_call<'a>(
_env: &mut Env<'a, '_>,
call: Call<'a>,
@ -6250,6 +6246,38 @@ fn build_call<'a>(
Stmt::Let(assigned, Expr::Call(call), return_layout, hole)
}
/// See https://github.com/rtfeldman/roc/issues/1549
///
/// What happened is that a function has a type error, but the arguments are not processed.
/// That means specializations were missing. Normally that is not a problem, but because
/// of our closure strategy, internal functions can "leak". That's what happened here.
///
/// The solution is to evaluate the arguments as normal, and only when calling the function give an error
fn evaluate_arguments_then_runtime_error<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
msg: String,
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
) -> Stmt<'a> {
let arena = env.arena;
// eventually we will throw this runtime error
let result = Stmt::RuntimeError(env.arena.alloc(msg));
// but, we also still evaluate and specialize the arguments to give better error messages
let arg_symbols = Vec::from_iter_in(
loc_args
.iter()
.map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)),
arena,
)
.into_bump_slice();
let iter = loc_args.into_iter().rev().zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
#[allow(clippy::too_many_arguments)]
fn call_by_name<'a>(
env: &mut Env<'a, '_>,
@ -6268,14 +6296,16 @@ fn call_by_name<'a>(
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
var, proc_name, fn_var
);
Stmt::RuntimeError(env.arena.alloc(msg))
evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args)
}
Err(LayoutProblem::Erroneous) => {
let msg = format!(
"Hit an erroneous type when creating a layout for {:?}",
proc_name
);
Stmt::RuntimeError(env.arena.alloc(msg))
evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args)
}
Ok(RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout)) => {
if procs.module_thunks.contains(&proc_name) {
@ -6821,6 +6851,7 @@ pub enum Pattern<'a> {
Underscore,
IntLiteral(i128),
FloatLiteral(u64),
DecimalLiteral(RocDec),
BitLiteral {
value: bool,
tag_name: TagName,
@ -6897,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,
@ -6909,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))
}
}
}
@ -6927,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,
@ -7346,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);
@ -7487,7 +7544,9 @@ fn from_can_record_destruct<'a>(
})
}
#[derive(Debug)]
pub enum IntPrecision {
Usize,
I128,
I64,
I32,
@ -7523,6 +7582,7 @@ fn int_precision_to_builtin(precision: IntPrecision) -> Builtin<'static> {
I32 => Builtin::Int32,
I16 => Builtin::Int16,
I8 => Builtin::Int8,
Usize => Builtin::Usize,
}
}
@ -7621,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!(
@ -7920,7 +7972,15 @@ fn union_lambda_set_to_switch<'a>(
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
debug_assert!(!lambda_set.is_empty());
if lambda_set.is_empty() {
// NOTE this can happen if there is a type error somewhere. Since the lambda set is empty,
// there is really nothing we can do here. We generate a runtime error here which allows
// code gen to proceed. We then assume that we hit another (more descriptive) error before
// hitting this one
let msg = "a Lambda Set isempty. Most likely there is a type error in your program.";
return Stmt::RuntimeError(msg);
}
let join_point_id = JoinPointId(env.unique_symbol());

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(),
@ -554,10 +558,10 @@ impl<'a> LambdaSet<'a> {
}
Ok(()) | Err((_, Content::FlexVar(_))) => {
// TODO hack for builting functions.
// this can happen when there is a type error somewhere
Ok(LambdaSet {
set: &[],
representation: arena.alloc(Layout::Struct(&[])),
representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))),
})
}
_ => panic!("called LambdaSet.from_var on invalid input"),
@ -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(&[])),
@ -2222,7 +2244,7 @@ fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
}
#[cfg(not(debug_assertions))]
fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
fn ext_var_is_empty_record(_subs: &Subs, _ext_var: Variable) -> bool {
// This should only ever be used in debug_assert! macros
unreachable!();
}

View File

@ -135,28 +135,6 @@ fn function_s<'a, 'i>(
arena.alloc(new_join)
}
Invoke {
symbol,
call,
layout,
pass,
fail,
exception_id,
} => {
let new_pass = function_s(env, w, c, pass);
let new_fail = function_s(env, w, c, fail);
let new_invoke = Invoke {
symbol: *symbol,
call: call.clone(),
layout: *layout,
pass: new_pass,
fail: new_fail,
exception_id: *exception_id,
};
arena.alloc(new_invoke)
}
Switch {
cond_symbol,
cond_layout,
@ -195,7 +173,7 @@ fn function_s<'a, 'i>(
arena.alloc(new_refcounting)
}
}
Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
}
}
@ -318,37 +296,6 @@ fn function_d_main<'a, 'i>(
}
}
}
Invoke {
symbol,
call,
layout,
pass,
fail,
exception_id,
} => {
if has_live_var(&env.jp_live_vars, stmt, x) {
let new_pass = {
let temp = function_d_main(env, x, c, pass);
function_d_finalize(env, x, c, temp)
};
let new_fail = {
let temp = function_d_main(env, x, c, fail);
function_d_finalize(env, x, c, temp)
};
let new_switch = Invoke {
symbol: *symbol,
call: call.clone(),
layout: *layout,
pass: new_pass,
fail: new_fail,
exception_id: *exception_id,
};
(arena.alloc(new_switch), true)
} else {
(stmt, false)
}
}
Switch {
cond_symbol,
cond_layout,
@ -433,9 +380,7 @@ fn function_d_main<'a, 'i>(
(arena.alloc(new_join), found)
}
Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => {
(stmt, has_live_var(&env.jp_live_vars, stmt, x))
}
Ret(_) | Jump(_, _) | RuntimeError(_) => (stmt, has_live_var(&env.jp_live_vars, stmt, x)),
}
}
@ -550,36 +495,13 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
arena.alloc(Let(*symbol, expr.clone(), *layout, b))
}
Invoke {
symbol,
call,
layout,
pass,
fail,
exception_id,
} => {
let branch_info = BranchInfo::None;
let new_pass = function_r_branch_body(env, &branch_info, pass);
let new_fail = function_r_branch_body(env, &branch_info, fail);
let invoke = Invoke {
symbol: *symbol,
call: call.clone(),
layout: *layout,
pass: new_pass,
fail: new_fail,
exception_id: *exception_id,
};
arena.alloc(invoke)
}
Refcounting(modify_rc, continuation) => {
let b = function_r(env, continuation);
arena.alloc(Refcounting(*modify_rc, b))
}
Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | RuntimeError(_) => {
// terminals
stmt
}
@ -594,19 +516,6 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym
debug_assert_ne!(*s, needle);
has_live_var_expr(e, needle) || has_live_var(jp_live_vars, c, needle)
}
Invoke {
symbol,
call,
pass,
fail,
..
} => {
debug_assert_ne!(*symbol, needle);
has_live_var_call(call, needle)
|| has_live_var(jp_live_vars, pass, needle)
|| has_live_var(jp_live_vars, fail, needle)
}
Switch { cond_symbol, .. } if *cond_symbol == needle => true,
Switch {
branches,
@ -647,7 +556,7 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym
Jump(id, arguments) => {
arguments.iter().any(|s| *s == needle) || jp_live_vars[id].contains(&needle)
}
Resume(_) | RuntimeError(_) => false,
RuntimeError(_) => false,
}
}

View File

@ -92,28 +92,6 @@ fn insert_jumps<'a>(
Some(arena.alloc(jump))
}
Invoke {
symbol,
call:
crate::ir::Call {
call_type: CallType::ByName { name: fsym, .. },
arguments,
..
},
fail,
pass: Stmt::Ret(rsym),
exception_id,
..
} if needle == *fsym && symbol == rsym => {
debug_assert_eq!(fail, &&Stmt::Resume(*exception_id));
// replace the call and return with a jump
let jump = Stmt::Jump(goal_id, arguments);
Some(arena.alloc(jump))
}
Let(symbol, expr, layout, cont) => {
let opt_cont = insert_jumps(arena, cont, goal_id, needle);
@ -126,36 +104,6 @@ fn insert_jumps<'a>(
}
}
Invoke {
symbol,
call,
fail,
pass,
layout,
exception_id,
} => {
let opt_pass = insert_jumps(arena, pass, goal_id, needle);
let opt_fail = insert_jumps(arena, fail, goal_id, needle);
if opt_pass.is_some() || opt_fail.is_some() {
let pass = opt_pass.unwrap_or(pass);
let fail = opt_fail.unwrap_or(fail);
let stmt = Invoke {
symbol: *symbol,
call: call.clone(),
layout: *layout,
pass,
fail,
exception_id: *exception_id,
};
Some(arena.alloc(stmt))
} else {
None
}
}
Join {
id,
parameters,
@ -241,7 +189,6 @@ fn insert_jumps<'a>(
None => None,
},
Resume(_) => None,
Ret(_) => None,
Jump(_, _) => None,
RuntimeError(_) => None,

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!(
@ -2683,26 +2683,50 @@ fn list_walk_until() {
}
#[test]
#[ignore]
fn int_literal_not_specialized() {
fn int_literal_not_specialized_with_annotation() {
// see https://github.com/rtfeldman/roc/issues/1600
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
satisfy : (U8 -> Bool) -> Str
satisfy = \_ -> "foo"
main : I64
main =
p1 = (\u -> u == 97)
satisfy : (U8 -> Str) -> Str
satisfy = \_ -> "foo"
satisfyA = satisfy p1
myEq : a, a -> Str
myEq = \_, _ -> "bar"
when satisfyA is
p1 : Num * -> Str
p1 = (\u -> myEq u 64)
when satisfy p1 is
_ -> 32
"#
),
32,
i64
);
}
#[test]
fn int_literal_not_specialized_no_annotation() {
// see https://github.com/rtfeldman/roc/issues/1600
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main =
satisfy : (U8 -> Str) -> Str
satisfy = \_ -> "foo"
myEq : a, a -> Str
myEq = \_, _ -> "bar"
p1 = (\u -> myEq u 64)
when satisfy p1 is
_ -> 32
"#
),
@ -2712,7 +2736,6 @@ fn int_literal_not_specialized() {
}
#[test]
#[ignore]
fn unresolved_tvar_when_capture_is_unused() {
// see https://github.com/rtfeldman/roc/issues/1585
assert_evals_to!(
@ -2725,8 +2748,7 @@ fn unresolved_tvar_when_capture_is_unused() {
r : Bool
r = False
# underscore does not change the problem, maybe it's type-related? We don 't really know what `Green` refers to below
p1 = (\x -> r == (1 == 1))
p1 = (\_ -> r == (1 == 1))
oneOfResult = List.map [p1] (\p -> p Green)
when oneOfResult is
@ -2738,3 +2760,21 @@ fn unresolved_tvar_when_capture_is_unused() {
i64
);
}
#[test]
#[should_panic(expected = "Roc failed with message: ")]
fn value_not_exposed_hits_panic() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main : I64
main =
Str.toInt 32
"#
),
32,
i64
);
}

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

@ -327,6 +327,14 @@ fn subs_fmt_desc(this: &Descriptor, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
write!(f, " m: {:?}", &this.mark)
}
pub struct SubsFmtContent<'a>(pub &'a Content, pub &'a Subs);
impl<'a> fmt::Debug for SubsFmtContent<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
subs_fmt_content(self.0, self.1, f)
}
}
fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result {
match this {
Content::FlexVar(name) => write!(f, "Flex({:?})", name),
@ -345,6 +353,14 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
}
}
pub struct SubsFmtFlatType<'a>(pub &'a FlatType, pub &'a Subs);
impl<'a> fmt::Debug for SubsFmtFlatType<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
subs_fmt_flat_type(self.0, self.1, f)
}
}
fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result {
match this {
FlatType::Apply(name, arguments) => {
@ -945,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

@ -1076,8 +1076,8 @@ fn unify_flat_type(
// any other combination is a mismatch
mismatch!(
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
other1,
other2
roc_types::subs::SubsFmtFlatType(other1, subs),
roc_types::subs::SubsFmtFlatType(other2, 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

View File

@ -1,3 +1,3 @@
pub const NOTHING_OPENED: &str = "Opening files is not yet supported. Execute `cargo run edit` from the root folder of the repo to try the editor.";
pub const NOTHING_OPENED: &str = "Opening files is not yet supported, execute `cargo run edit` from the root folder of the repo to try the editor.";
pub const START_TIP: &str =
"Start by typing '[', '{', '\"' or a number.\nInput chars that would create parse errors will be ignored.";

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

@ -12,10 +12,6 @@ To run in release mode instead, do:
$ cargo run --release Hello.roc
```
## Troubleshooting
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.
## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure

View File

@ -12,10 +12,6 @@ To run in release mode instead, do:
$ cargo run --release Hello.roc
```
## Troubleshooting
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.
## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure

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

@ -11,7 +11,3 @@ To run in release mode instead, do:
```bash
$ cargo run --release Quicksort.roc
```
## Troubleshooting
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.

View File

@ -91,12 +91,16 @@ impl<T> RocList<T> {
}
}
fn get_storage_ptr(&self) -> *const isize {
let ptr = self.elements as *const isize;
fn get_storage_ptr_help(elements: *mut T) -> *mut isize {
let ptr = elements as *mut isize;
unsafe { ptr.offset(-1) }
}
fn get_storage_ptr(&self) -> *const isize {
Self::get_storage_ptr_help(self.elements)
}
fn get_storage_ptr_mut(&mut self) -> *mut isize {
self.get_storage_ptr() as *mut isize
}
@ -278,6 +282,103 @@ impl<T> RocList<T> {
fn align_of_storage_ptr() -> u32 {
mem::align_of::<T>().max(mem::align_of::<usize>()) as u32
}
unsafe fn drop_pointer_to_first_argument(ptr: *mut T) {
let storage_ptr = Self::get_storage_ptr_help(ptr);
let storage_val = *storage_ptr;
if storage_val == REFCOUNT_1 || storage_val > 0 {
// If we have no more references, or if this was unique,
// deallocate it.
roc_dealloc(storage_ptr as *mut c_void, Self::align_of_storage_ptr());
} else if storage_val < 0 {
// If this still has more references, decrement one.
*storage_ptr = storage_val - 1;
}
// The only remaining option is that this is in readonly memory,
// in which case we shouldn't attempt to do anything to it.
}
}
impl<'a, T> IntoIterator for &'a RocList<T> {
type Item = &'a T;
type IntoIter = <&'a [T] as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.as_slice().iter()
}
}
impl<T> IntoIterator for RocList<T> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
let remaining = self.len();
let buf = unsafe { NonNull::new_unchecked(self.elements as _) };
let ptr = self.elements;
IntoIter {
buf,
ptr,
remaining,
}
}
}
use core::ptr::NonNull;
pub struct IntoIter<T> {
buf: NonNull<T>,
// pub cap: usize,
ptr: *const T,
remaining: usize,
}
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
next_help(self)
}
}
fn next_help<T>(this: &mut IntoIter<T>) -> Option<T> {
if this.remaining == 0 {
None
} else if mem::size_of::<T>() == 0 {
// purposefully don't use 'ptr.offset' because for
// vectors with 0-size elements this would return the
// same pointer.
this.remaining -= 1;
// Make up a value of this ZST.
Some(unsafe { mem::zeroed() })
} else {
let old = this.ptr;
this.ptr = unsafe { this.ptr.offset(1) };
this.remaining -= 1;
Some(unsafe { ptr::read(old) })
}
}
impl<T> Drop for IntoIter<T> {
fn drop(&mut self) {
// drop the elements that we have not yet returned.
while let Some(item) = next_help(self) {
drop(item);
}
// deallocate the whole buffer
unsafe {
RocList::drop_pointer_to_first_argument(self.buf.as_mut());
}
}
}
impl<T> Default for RocList<T> {
@ -483,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;
@ -731,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!
@ -744,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.

View File

@ -53,7 +53,6 @@ let
llvmPkgs.libcxx
llvmPkgs.libcxxabi
libffi
libunwind
libxml2
ncurses
zlib
@ -76,14 +75,6 @@ in pkgs.mkShell {
NIXOS_GLIBC_PATH =
if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else "";
LD_LIBRARY_PATH = with pkgs;
lib.makeLibraryPath ([
pkg-config
stdenv.cc.cc.lib
llvmPkgs.libcxx
llvmPkgs.libcxxabi
libunwind
libffi
ncurses
zlib
] ++ linuxInputs);
lib.makeLibraryPath
([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] ++ linuxInputs);
}