Merge branch 'trunk' of github.com:rtfeldman/roc into editor-let-value

This commit is contained in:
Anton-4 2021-09-03 14:44:21 +02:00
commit 3397d780b8
100 changed files with 4704 additions and 1253 deletions

View file

@ -175,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: 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. 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. 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. 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. 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.

747
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ install-other-libs:
FROM +prep-debian FROM +prep-debian
RUN apt -y install wget git RUN apt -y install wget git
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard 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: install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs FROM +install-other-libs
@ -48,49 +48,18 @@ install-zig-llvm-valgrind-clippy-rustfmt:
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ENV SCCACHE_DIR=/earthbuild/sccache_dir ENV SCCACHE_DIR=/earthbuild/sccache_dir
ENV CARGO_INCREMENTAL=0 # no need to recompile package when using new function 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: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt 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 --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: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./ COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode && ./run-tests.sh RUN cd bitcode && ./run-tests.sh
check-clippy: check-clippy:
FROM +copy-dirs-and-cache FROM +copy-dirs
RUN cargo clippy -V RUN cargo clippy -V
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings cargo clippy -- -D warnings
@ -106,8 +75,11 @@ check-typos:
RUN typos RUN typos
test-rust: test-rust:
FROM +copy-dirs-and-cache FROM +copy-dirs
ENV RUST_BACKTRACE=1 ENV RUST_BACKTRACE=1
# run one of the benchmarks to make sure the host is compiled
# not pre-compiling the host can cause race conditions
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release && sccache --show-stats cargo test --release && sccache --show-stats
@ -132,7 +104,7 @@ test-all:
# compile everything needed for benchmarks and output a self-contained folder # compile everything needed for benchmarks and output a self-contained folder
prep-bench-folder: prep-bench-folder:
FROM +copy-dirs-and-cache FROM +copy-dirs
ARG BENCH_SUFFIX=branch ARG BENCH_SUFFIX=branch
RUN cargo criterion -V RUN cargo criterion -V
RUN --mount=type=cache,target=$SCCACHE_DIR cd cli && cargo criterion --no-run 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 - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}" add-apt-repository "${REPO_NAME}"
apt-get update 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 wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
tar -xf valgrind-3.16.1.tar.bz2 tar -xf valgrind-3.16.1.tar.bz2

View file

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

View file

@ -74,10 +74,30 @@ pub fn build_file<'a>(
builtin_defs_map, 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 path_to_platform = loaded.platform_path.clone();
let app_o_file = Builder::new() let app_o_file = Builder::new()
.prefix("roc_app") .prefix("roc_app")
.suffix(".o") .suffix(&format!(".{}", app_extension))
.tempfile() .tempfile()
.map_err(|err| { .map_err(|err| {
todo!("TODO Gracefully handle tempfile creation error {:?}", err); todo!("TODO Gracefully handle tempfile creation error {:?}", err);
@ -131,7 +151,7 @@ pub fn build_file<'a>(
arena, arena,
loaded, loaded,
&roc_file_path, &roc_file_path,
Triple::host(), target,
app_o_file, app_o_file,
opt_level, opt_level,
emit_debug_info, emit_debug_info,
@ -173,12 +193,13 @@ pub fn build_file<'a>(
let mut host_input_path = PathBuf::from(cwd); let mut host_input_path = PathBuf::from(cwd);
host_input_path.push(&*path_to_platform); 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 // 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. // a package repository, as we can then get precompiled hosts from there.
let rebuild_host_start = SystemTime::now(); 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(); let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
if emit_debug_info { if emit_debug_info {

View file

@ -15,7 +15,8 @@ use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process; use std::process;
use std::process::Command; use std::process::Command;
use target_lexicon::Triple; use target_lexicon::BinaryFormat;
use target_lexicon::{Architecture, Triple};
pub mod build; pub mod build;
pub mod repl; pub mod repl;
@ -29,7 +30,9 @@ pub const CMD_DOCS: &str = "docs";
pub const FLAG_DEBUG: &str = "debug"; pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_OPTIMIZE: &str = "optimize"; pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_LIB: &str = "lib"; pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend";
pub const ROC_FILE: &str = "ROC_FILE"; pub const ROC_FILE: &str = "ROC_FILE";
pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; 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.)") .help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false), .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(
Arg::with_name(FLAG_LIB) Arg::with_name(FLAG_LIB)
.long(FLAG_LIB) .long(FLAG_LIB)
@ -118,6 +130,15 @@ pub fn build_app<'a>() -> App<'a> {
.requires(ROC_FILE) .requires(ROC_FILE)
.required(false), .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(
Arg::with_name(ROC_FILE) Arg::with_name(ROC_FILE)
.help("The .roc file of an app to build and run") .help("The .roc file of an app to build and run")
@ -159,11 +180,25 @@ pub enum BuildConfig {
BuildAndRun { roc_file_arg_index: usize }, 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")] #[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 build::build_file;
use BuildConfig::*; use BuildConfig::*;
let target = match matches.value_of(FLAG_BACKEND) {
Some("wasm") => wasm32_target_tripple(),
_ => Triple::host(),
};
let arena = Bump::new(); let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap(); 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 src_dir = path.parent().unwrap().canonicalize().unwrap();
let res_binary_path = build_file( let res_binary_path = build_file(
&arena, &arena,
target, &target,
src_dir, src_dir,
path, path,
opt_level, opt_level,
@ -240,7 +275,30 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
Ok(outcome.status_code()) Ok(outcome.status_code())
} }
BuildAndRun { roc_file_arg_index } => { BuildAndRun { roc_file_arg_index } => {
let mut cmd = Command::new(binary_path); let mut cmd = match target.architecture {
Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
let args = std::env::args()
.skip(roc_file_arg_index)
.collect::<Vec<_>>();
run_with_wasmer(generated_filename, &args);
return Ok(0);
}
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = target.architecture {
cmd.arg(binary_path);
}
// Forward all the arguments after the .roc file argument // Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like: // to the new process. This way, you can do things like:
@ -306,3 +364,26 @@ fn roc_run(cmd: &mut Command) -> io::Result<i32> {
} }
} }
} }
fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
use wasmer::{Instance, Module, Store};
let store = Store::default();
let module = Module::from_file(&store, &wasm_path).unwrap();
// First, we create the `WasiEnv`
use wasmer_wasi::WasiState;
let mut wasi_env = WasiState::new("hello").args(args).finalize().unwrap();
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let import_object = wasi_env
.import_object(&module)
.unwrap_or_else(|_| wasmer::imports!());
let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").unwrap();
start.call(&[]).unwrap();
}

View file

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

View file

@ -222,7 +222,8 @@ fn jit_to_ast_help<'a>(
let tags_map: roc_collections::all::MutMap<_, _> = let tags_map: roc_collections::all::MutMap<_, _> =
tags_vec.iter().cloned().collect(); 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); let size = layout.stack_size(env.ptr_bytes);
use roc_mono::layout::WrappedVariant::*; 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())) .map(|(a, b)| (a.clone(), b.to_vec()))
.collect(); .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 { match union_variant {
UnionVariant::ByteUnion(tagnames) => { UnionVariant::ByteUnion(tagnames) => {

View file

@ -132,7 +132,7 @@ pub fn gen_and_eval<'a>(
let builder = context.create_builder(); let builder = context.create_builder();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&context, "", &context, "", ptr_bytes,
)); ));
// mark our zig-defined builtins as internal // mark our zig-defined builtins as internal
@ -179,7 +179,7 @@ pub fn gen_and_eval<'a>(
interns, interns,
module, module,
ptr_bytes, ptr_bytes,
is_gen_test: false, is_gen_test: true, // so roc_panic is generated
// important! we don't want any procedures to get the C calling convention // important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(), exposed_to_host: MutSet::default(),
}; };
@ -197,6 +197,9 @@ pub fn gen_and_eval<'a>(
env.dibuilder.finalize(); env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output: // Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();

View file

@ -108,6 +108,35 @@ mod cli_run {
assert!(out.status.success()); 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 path = file.with_file_name(executable_filename);
let stdout = crate::run_with_wasmer(&path, stdin);
if !stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, stdout
);
}
}
/// This macro does two things. /// This macro does two things.
/// ///
/// First, it generates and runs a separate test for each of the given /// First, it generates and runs a separate test for each of the given
@ -223,13 +252,13 @@ mod cli_run {
// expected_ending: "", // expected_ending: "",
// use_valgrind: true, // use_valgrind: true,
// }, // },
// cli:"cli" => Example { cli:"cli" => Example {
// filename: "Echo.roc", filename: "Echo.roc",
// executable_filename: "echo", executable_filename: "echo",
// stdin: &["Giovanni\n", "Giorgio\n"], stdin: &["Giovanni\n", "Giorgio\n"],
// expected_ending: "Giovanni Giorgio!\n", expected_ending: "Hi, Giovanni Giorgio!\n",
// use_valgrind: true, use_valgrind: true,
// }, },
// custom_malloc:"custom-malloc" => Example { // custom_malloc:"custom-malloc" => Example {
// filename: "Main.roc", // filename: "Main.roc",
// executable_filename: "custom-malloc-example", // executable_filename: "custom-malloc-example",
@ -255,9 +284,9 @@ mod cli_run {
let benchmark = $benchmark; let benchmark = $benchmark;
let file_name = examples_dir("benchmarks").join(benchmark.filename); 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 { match benchmark.filename {
"QuicksortApp.roc" | "RBTreeCk.roc" => { "QuicksortApp.roc" => {
eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename); eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename);
return; return;
} }
@ -283,8 +312,48 @@ mod cli_run {
benchmark.use_valgrind, 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] #[test]
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
fn all_benchmarks_have_tests() { fn all_benchmarks_have_tests() {
@ -326,8 +395,8 @@ mod cli_run {
rbtree_ck => Example { rbtree_ck => Example {
filename: "RBTreeCk.roc", filename: "RBTreeCk.roc",
executable_filename: "rbtree-ck", executable_filename: "rbtree-ck",
stdin: &[], stdin: &["100"],
expected_ending: "Node Black 0 {} Empty Empty\n", expected_ending: "10\n",
use_valgrind: true, use_valgrind: true,
}, },
rbtree_insert => Example { rbtree_insert => Example {
@ -492,3 +561,58 @@ mod cli_run {
); );
} }
} }
#[cfg(feature = "wasm-cli-run")]
fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
use std::io::Write;
use wasmer::{Instance, Module, Store};
let store = Store::default();
let module = Module::from_file(&store, &wasm_path).unwrap();
let mut fake_stdin = wasmer_wasi::Pipe::new();
let fake_stdout = wasmer_wasi::Pipe::new();
let fake_stderr = wasmer_wasi::Pipe::new();
for line in stdin {
write!(fake_stdin, "{}", line).unwrap();
}
// First, we create the `WasiEnv`
use wasmer_wasi::WasiState;
let mut wasi_env = WasiState::new("hello")
.stdin(Box::new(fake_stdin))
.stdout(Box::new(fake_stdout))
.stderr(Box::new(fake_stderr))
.finalize()
.unwrap();
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let import_object = wasi_env
.import_object(&module)
.unwrap_or_else(|_| wasmer::imports!());
let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").unwrap();
match start.call(&[]) {
Ok(_) => {
let mut state = wasi_env.state.lock().unwrap();
match state.fs.stdout_mut() {
Ok(Some(stdout)) => {
let mut buf = String::new();
stdout.read_to_string(&mut buf).unwrap();
return buf;
}
_ => todo!(),
}
}
Err(e) => {
panic!("Something went wrong running a wasm test:\n{:?}", e);
}
}
}

View file

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

View file

@ -26,6 +26,10 @@ pub fn link(
link_type: LinkType, link_type: LinkType,
) -> io::Result<(Child, PathBuf)> { ) -> io::Result<(Child, PathBuf)> {
match target { match target {
Triple {
architecture: Architecture::Wasm32,
..
} => link_wasm32(target, output_path, input_paths, link_type),
Triple { Triple {
operating_system: OperatingSystem::Linux, operating_system: OperatingSystem::Linux,
.. ..
@ -56,7 +60,7 @@ fn find_zig_str_path() -> PathBuf {
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
pub fn build_zig_host( pub fn build_zig_host_native(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
emit_bin: &str, emit_bin: &str,
@ -86,7 +90,7 @@ pub fn build_zig_host(
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub fn build_zig_host( pub fn build_zig_host_native(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
emit_bin: &str, emit_bin: &str,
@ -158,21 +162,62 @@ pub fn build_zig_host(
.unwrap() .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_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o"); 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 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_src = host_input_path.with_file_name("host.rs");
let rust_host_dest = host_input_path.with_file_name("rust_host.o"); 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 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_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
let env_home = env::var("HOME").unwrap_or_else(|_| "".to_string()); let env_home = env::var("HOME").unwrap_or_else(|_| "".to_string());
if zig_host_src.exists() { if zig_host_src.exists() {
// Compile host.zig // Compile host.zig
let emit_bin = format!("-femit-bin={}", host_dest.to_str().unwrap());
let zig_str_path = find_zig_str_path(); let zig_str_path = find_zig_str_path();
@ -182,17 +227,31 @@ pub fn rebuild_host(host_input_path: &Path) {
&zig_str_path &zig_str_path
); );
validate_output( let output = match target.architecture {
"host.zig", Architecture::Wasm32 => {
"zig", let emit_bin = format!("-femit-llvm-ir={}", host_dest_wasm.to_str().unwrap());
build_zig_host( build_zig_host_wasm32(
&env_path, &env_path,
&env_home, &env_home,
&emit_bin, &emit_bin,
zig_host_src.to_str().unwrap(), zig_host_src.to_str().unwrap(),
zig_str_path.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 { } else {
// Compile host.c // Compile host.c
let output = Command::new("clang") let output = Command::new("clang")
@ -233,7 +292,7 @@ pub fn rebuild_host(host_input_path: &Path) {
c_host_dest.to_str().unwrap(), c_host_dest.to_str().unwrap(),
"-lhost", "-lhost",
"-o", "-o",
host_dest.to_str().unwrap(), host_dest_native.to_str().unwrap(),
]) ])
.output() .output()
.unwrap(); .unwrap();
@ -260,7 +319,7 @@ pub fn rebuild_host(host_input_path: &Path) {
c_host_dest.to_str().unwrap(), c_host_dest.to_str().unwrap(),
rust_host_dest.to_str().unwrap(), rust_host_dest.to_str().unwrap(),
"-o", "-o",
host_dest.to_str().unwrap(), host_dest_native.to_str().unwrap(),
]) ])
.output() .output()
.unwrap(); .unwrap();
@ -283,7 +342,7 @@ pub fn rebuild_host(host_input_path: &Path) {
// Clean up c_host.o // Clean up c_host.o
let output = Command::new("mv") let output = Command::new("mv")
.env_clear() .env_clear()
.args(&[c_host_dest, host_dest]) .args(&[c_host_dest, host_dest_native])
.output() .output()
.unwrap(); .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")] #[cfg(feature = "llvm")]
pub fn module_to_dylib( pub fn module_to_dylib(
module: &inkwell::module::Module, module: &inkwell::module::Module,

View file

@ -29,7 +29,7 @@ pub fn gen_from_mono_module(
arena: &bumpalo::Bump, arena: &bumpalo::Bump,
mut loaded: MonomorphizedModule, mut loaded: MonomorphizedModule,
roc_file_path: &Path, roc_file_path: &Path,
target: target_lexicon::Triple, target: &target_lexicon::Triple,
app_o_file: &Path, app_o_file: &Path,
opt_level: OptLevel, opt_level: OptLevel,
emit_debug_info: bool, emit_debug_info: bool,
@ -93,8 +93,9 @@ pub fn gen_from_mono_module(
} }
// Generate the binary // Generate the binary
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let context = Context::create(); 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 // strip Zig debug stuff
// module.strip_debug_info(); // module.strip_debug_info();
@ -124,6 +125,7 @@ pub fn gen_from_mono_module(
|| name.starts_with("roc_builtins.dec") || name.starts_with("roc_builtins.dec")
|| name.starts_with("list.RocList") || name.starts_with("list.RocList")
|| name.starts_with("dict.RocDict") || name.starts_with("dict.RocDict")
|| name.contains("decref")
{ {
function.add_attribute(AttributeLoc::Function, enum_attr); function.add_attribute(AttributeLoc::Function, enum_attr);
} }
@ -134,7 +136,6 @@ pub fn gen_from_mono_module(
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
// Compile and add all the Procs before adding main // 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 { let env = roc_gen_llvm::llvm::build::Env {
arena, arena,
builder: &builder, builder: &builder,
@ -219,47 +220,79 @@ pub fn gen_from_mono_module(
} }
} }
// assemble the .ll into a .bc use target_lexicon::Architecture;
let _ = Command::new("llvm-as") match target.architecture {
.args(&[ Architecture::X86_64 | Architecture::Aarch64(_) => {
app_ll_dbg_file.to_str().unwrap(), // assemble the .ll into a .bc
"-o", let _ = Command::new("llvm-as")
app_bc_file.to_str().unwrap(), .args(&[
]) app_ll_dbg_file.to_str().unwrap(),
.output() "-o",
.unwrap(); app_bc_file.to_str().unwrap(),
])
.output()
.unwrap();
let llc_args = &[ let llc_args = &[
"-filetype=obj", "-filetype=obj",
app_bc_file.to_str().unwrap(), app_bc_file.to_str().unwrap(),
"-o", "-o",
app_o_file.to_str().unwrap(), app_o_file.to_str().unwrap(),
]; ];
// write the .o file. Note that this builds the .o for the local machine, // write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely. // and ignores the `target_machine` entirely.
// //
// different systems name this executable differently, so we shotgun for // different systems name this executable differently, so we shotgun for
// the most common ones and then give up. // the most common ones and then give up.
let _: Result<std::process::Output, std::io::Error> = let _: Result<std::process::Output, std::io::Error> =
Command::new(format!("llc-{}", LLVM_VERSION)) Command::new(format!("llc-{}", LLVM_VERSION))
.args(llc_args) .args(llc_args)
.output() .output()
.or_else(|_| Command::new("llc").args(llc_args).output()) .or_else(|_| Command::new("llc").args(llc_args).output())
.map_err(|_| { .map_err(|_| {
panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION); 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 { } else {
// Emit the .o file // 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; target_machine
let model = CodeModel::Default; .write_to_file(env.module, FileType::Object, app_o_file)
let target_machine = .expect("Writing .o file failed");
target::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap(); }
Architecture::Wasm32 => {
target_machine // Useful for debugging
.write_to_file(env.module, FileType::Object, app_o_file) // module.print_to_file(app_ll_file);
.expect("Writing .o file failed"); 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(); 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, operating_system: OperatingSystem::Linux,
.. ..
} => "x86_64-unknown-linux-gnu", } => "x86_64-unknown-linux-gnu",
Triple {
architecture: Architecture::Wasm32,
..
} => "wasm32-unknown-unknown",
Triple { Triple {
architecture: Architecture::Aarch64(_), architecture: Architecture::Aarch64(_),
operating_system: OperatingSystem::Linux, 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); test_step.dependOn(&main_tests.step);
// LLVM IR // LLVM IR
const obj_name = "builtins"; const obj_name = "builtins-64bit";
const llvm_obj = b.addObject(obj_name, main_path); const llvm_obj = b.addObject(obj_name, main_path);
llvm_obj.setBuildMode(mode); llvm_obj.setBuildMode(mode);
llvm_obj.linkSystemLibrary("c"); llvm_obj.linkSystemLibrary("c");
@ -30,6 +30,30 @@ pub fn build(b: *Builder) void {
const ir = b.step("ir", "Build LLVM ir"); const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step); ir.dependOn(&llvm_obj.step);
// LLVM IR 32-bit (wasm)
var target = b.standardTargetOptions(.{});
// 32-bit x86, useful for debugging
// target.os_tag = std.Target.Os.Tag.linux;
// target.cpu_arch = std.Target.Cpu.Arch.i386;
// target.abi = std.Target.Abi.musl;
// 32-bit wasm
target.os_tag = std.Target.Os.Tag.wasi;
target.cpu_arch = std.Target.Cpu.Arch.wasm32;
target.abi = std.Target.Abi.none;
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.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 // Object File
// TODO: figure out how to get this to emit symbols that are only scoped to linkage (global but hidden). // 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. // 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 INITIAL_SEED = 0xc70f6907;
const InPlace = packed enum(u8) { const InPlace = enum(u8) {
InPlace, InPlace,
Clone, Clone,
}; };
const Slot = packed enum(u8) { const Slot = enum(u8) {
Empty, Empty,
Filled, Filled,
PreviouslyFilled, PreviouslyFilled,
@ -63,27 +63,24 @@ fn capacityOfLevel(input: usize) usize {
// alignment of the key and value. The tag furthermore indicates // alignment of the key and value. The tag furthermore indicates
// which has the biggest aligmnent. If both are the same, we put // which has the biggest aligmnent. If both are the same, we put
// the key first // the key first
const Alignment = packed enum(u8) { const Alignment = extern struct {
Align16KeyFirst, bits: u8,
Align16ValueFirst,
Align8KeyFirst, const VALUE_BEFORE_KEY_FLAG: u8 = 0b1000_0000;
Align8ValueFirst,
fn toU32(self: Alignment) u32 { fn toU32(self: Alignment) u32 {
switch (self) { if (self.bits >= VALUE_BEFORE_KEY_FLAG) {
.Align16KeyFirst => return 16, return self.bits ^ Alignment.VALUE_BEFORE_KEY_FLAG;
.Align16ValueFirst => return 16, } else {
.Align8KeyFirst => return 8, return self.bits;
.Align8ValueFirst => return 8,
} }
} }
fn keyFirst(self: Alignment) bool { fn keyFirst(self: Alignment) bool {
switch (self) { if (self.bits & Alignment.VALUE_BEFORE_KEY_FLAG > 0) {
.Align16KeyFirst => return true, return false;
.Align16ValueFirst => return false, } else {
.Align8KeyFirst => return true, return true;
.Align8ValueFirst => return false,
} }
} }
}; };
@ -359,7 +356,7 @@ pub const RocDict = extern struct {
// hash the key, and modulo by the maximum size // hash the key, and modulo by the maximum size
// (so we get an in-bounds index) // (so we get an in-bounds index)
const hash = hash_fn(seed, key); 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)) { switch (self.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => { Slot.Empty, Slot.PreviouslyFilled => {
@ -386,8 +383,8 @@ pub const RocDict = extern struct {
}; };
// Dict.empty // Dict.empty
pub fn dictEmpty() callconv(.C) RocDict { pub fn dictEmpty(dict: *RocDict) callconv(.C) void {
return RocDict.empty(); dict.* = RocDict.empty();
} }
pub fn slotSize(key_size: usize, value_size: usize) usize { pub fn slotSize(key_size: usize, value_size: usize) usize {
@ -426,7 +423,7 @@ pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width:
} }
const hash = hash_fn(seed, key); 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()); assert(index < result.capacity());
switch (result.getSlot(index, key_width, value_width)) { switch (result.getSlot(index, key_width, value_width)) {

View file

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

View file

@ -109,6 +109,7 @@ comptime {
const utils = @import("utils.zig"); const utils = @import("utils.zig");
comptime { comptime {
exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.decrefC, "decref");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
} }

View file

@ -1,5 +1,4 @@
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const roc_mem = @import("mem.zig");
const RocList = @import("list.zig").RocList; const RocList = @import("list.zig").RocList;
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
@ -10,7 +9,7 @@ const expectEqual = testing.expectEqual;
const expectError = testing.expectError; const expectError = testing.expectError;
const expect = testing.expect; const expect = testing.expect;
const InPlace = packed enum(u8) { const InPlace = enum(u8) {
InPlace, InPlace,
Clone, Clone,
}; };
@ -52,7 +51,7 @@ pub const RocStr = extern struct {
return result; 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)); const first_element = utils.allocateWithRefcount(number_of_chars, @sizeOf(usize));
return RocStr{ 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 strings. Otherwise, we need to allocate and copy a new
// null-terminated string, which has a much higher performance cost! // null-terminated string, which has a much higher performance cost!
fn isNullTerminated(self: RocStr) bool { fn isNullTerminated(self: RocStr) bool {
const len = self.len(); const length = self.len();
const longest_small_str = @sizeOf(RocStr) - 1; const longest_small_str = @sizeOf(RocStr) - 1;
// NOTE: We want to compare length here, *NOT* check for is_small_str! // 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 // (The other branch dereferences the bytes pointer, which is not safe
// to do for the empty string.) // 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 // 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 // end of the string will be zero. (Small strings set all their
// unused bytes to 0, so that comparison for equality can be fast.) // 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, // Also, if we are exactly a maximum-length small string,
// then the next byte is off the end of the struct; // then the next byte is off the end of the struct;
// in that case, we are also not null-terminated! // 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 { } else {
// This is a big string, and it's not empty, so we can safely // This is a big string, and it's not empty, so we can safely
// dereference the pointer. // dereference the pointer.
@ -253,8 +252,8 @@ pub const RocStr = extern struct {
// //
// If we have excess capacity, then we can safely read the next // 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! // byte after the end of the string. Maybe it happens to be zero!
if (capacity_or_refcount > @intCast(isize, len)) { if (capacity_or_refcount > @intCast(isize, length)) {
return self.str_bytes[len] == 0; return self.str_bytes[length] == 0;
} else { } else {
// This string was refcounted or immortal; we can't safely read // This string was refcounted or immortal; we can't safely read
// the next byte, so assume the string is not null-terminated. // 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 0 for refcounted stirngs and immortal strings.
// Returns the stored capacity value for all other strings. // Returns the stored capacity value for all other strings.
pub fn capacity(self: RocStr) usize { pub fn capacity(self: RocStr) usize {
const len = self.len(); const length = self.len();
const longest_small_str = @sizeOf(RocStr) - 1; 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 // Note that although empty strings technically have the full
// capacity of a small string available, they aren't marked as small // 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 // 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 { pub fn asU8ptr(self: RocStr) [*]u8 {
// Since this conditional would be prone to branch misprediction, // Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov. // 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 // 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 { fn strFromIntHelp(comptime T: type, int: T) RocStr {
// determine maximum size for this T // 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 // the string representation of the minimum i128 value uses at most 40 characters
var buf: [40]u8 = undefined; var buf: [40]u8 = undefined;
var result = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable; 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 // Str.fromFloat
pub fn strFromFloatC(float: f64) callconv(.C) RocStr { pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
// NOTE the compiled zig for float formatting seems to use LLVM11-specific features return @call(.{ .modifier = always_inline }, strFromFloatHelp, .{ f64, float });
// 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 fn strFromFloatHelp(comptime T: type, float: T) RocStr {
@cDefine("_NO_CRT_STDIO_INLINE", "1");
@cInclude("stdio.h");
});
var buf: [100]u8 = undefined; 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, result.len);
return RocStr.init(&buf, @intCast(usize, result));
} }
// Str.split // Str.split
@ -785,8 +788,6 @@ pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
return count; return count;
} }
fn rocStrFromLiteral(bytes_arr: *const []u8) RocStr {}
test "countGraphemeClusters: empty string" { test "countGraphemeClusters: empty string" {
const count = countGraphemeClusters(RocStr.empty()); const count = countGraphemeClusters(RocStr.empty());
try expectEqual(count, 0); try expectEqual(count, 0);
@ -867,7 +868,6 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
// Str.startsWithCodePt // Str.startsWithCodePt
pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool { pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool {
const bytes_len = string.len();
const bytes_ptr = string.asU8ptr(); const bytes_ptr = string.asU8ptr();
var buffer: [4]u8 = undefined; var buffer: [4]u8 = undefined;
@ -1266,7 +1266,7 @@ pub fn numberOfNextCodepointBytes(ptr: [*]u8, len: usize, index: usize) Utf8Deco
// Return types for validateUtf8Bytes // Return types for validateUtf8Bytes
// Values must be in alphabetical order. That is, lowest values are the first alphabetically. // 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, CodepointTooLarge = 0,
EncodesSurrogateHalf = 1, EncodesSurrogateHalf = 1,
ExpectedContinuation = 2, 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); 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"); @panic("Roc paniced");
} }
@ -69,12 +72,14 @@ 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 // indirection because otherwise zig creats an alias to the panic function which our LLVM code
// does not know how to deal with // does not know how to deal with
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { 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(); // const stderr = std.io.getStdErr().writer();
// stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable; // stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable;
std.c.exit(1); // std.c.exit(1);
} }
pub const Inc = fn (?[*]u8) callconv(.C) void; pub const Inc = fn (?[*]u8) callconv(.C) void;
@ -99,6 +104,19 @@ pub const IntWidth = enum(u8) {
Usize, Usize,
}; };
pub fn decrefC(
bytes_or_null: ?[*]isize,
alignment: u32,
) callconv(.C) void {
// IMPORTANT: bytes_or_null is this case is expected to be a pointer to the refcount
// (NOT the start of the data, or the start of the allocation)
// this is of course unsafe, but we trust what we get from the llvm side
var bytes = @ptrCast([*]isize, bytes_or_null);
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ bytes, alignment });
}
pub fn decref( pub fn decref(
bytes_or_null: ?[*]u8, bytes_or_null: ?[*]u8,
data_bytes: usize, data_bytes: usize,
@ -110,72 +128,75 @@ pub fn decref(
var bytes = bytes_or_null orelse return; 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]; decref_ptr_to_refcount(isizes - 1, alignment);
const refcount_isize = @bitCast(isize, refcount); }
switch (alignment) { inline fn decref_ptr_to_refcount(
16 => { refcount_ptr: [*]isize,
if (refcount == REFCOUNT_ONE_ISIZE) { alignment: u32,
dealloc(bytes - 16, alignment); ) void {
} else if (refcount_isize < 0) { const refcount: isize = refcount_ptr[0];
(isizes - 1)[0] = refcount - 1; const extra_bytes = std.math.max(alignment, @sizeOf(usize));
}
}, if (refcount == REFCOUNT_ONE_ISIZE) {
else => { dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
// NOTE enums can currently have an alignment of < 8 } else if (refcount < 0) {
if (refcount == REFCOUNT_ONE_ISIZE) { refcount_ptr[0] = refcount - 1;
dealloc(bytes - 8, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
} }
} }
pub fn allocateWithRefcount( pub fn allocateWithRefcount(
data_bytes: usize, data_bytes: usize,
alignment: u32, element_alignment: u32,
) [*]u8 { ) [*]u8 {
const result_in_place = false; const alignment = std.math.max(@sizeOf(usize), element_alignment);
const first_slot_offset = std.math.max(@sizeOf(usize), element_alignment);
const length = alignment + data_bytes;
switch (alignment) { switch (alignment) {
16 => { 16 => {
const length = 2 * @sizeOf(usize) + data_bytes;
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment)); var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment));
var as_usize_array = @ptrCast([*]usize, new_bytes); var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place) { as_usize_array[0] = 0;
as_usize_array[0] = 0; as_usize_array[1] = REFCOUNT_ONE;
as_usize_array[1] = @intCast(usize, number_of_slots);
} else {
as_usize_array[0] = 0;
as_usize_array[1] = REFCOUNT_ONE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes); var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + 2 * @sizeOf(usize); const first_slot = as_u8_array + first_slot_offset;
return first_slot;
},
8 => {
var raw = alloc(length, alignment);
var new_bytes: [*]align(8) u8 = @alignCast(8, raw);
var as_isize_array = @ptrCast([*]isize, new_bytes);
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + first_slot_offset;
return first_slot;
},
4 => {
var raw = alloc(length, alignment);
var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw);
var as_isize_array = @ptrCast([*]isize, new_bytes);
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + first_slot_offset;
return first_slot; return first_slot;
}, },
else => { else => {
const length = @sizeOf(usize) + data_bytes; // const stdout = std.io.getStdOut().writer();
// stdout.print("alignment: {d}", .{alignment}) catch unreachable;
var new_bytes: [*]align(8) u8 = @alignCast(8, alloc(length, alignment)); // @panic("allocateWithRefcount with invalid alignment");
unreachable;
var as_isize_array = @ptrCast([*]isize, new_bytes);
if (result_in_place) {
as_isize_array[0] = @intCast(isize, number_of_slots);
} else {
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + @sizeOf(usize);
return first_slot;
}, },
} }
} }
@ -226,7 +247,7 @@ pub const RocResult = extern struct {
} }
}; };
pub const Ordering = packed enum(u8) { pub const Ordering = enum(u8) {
EQ = 0, EQ = 0,
GT = 1, GT = 1,
LT = 2, LT = 2,

View file

@ -24,39 +24,55 @@ fn main() {
return; 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 // "." is relative to where "build.rs" is
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap(); let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode"); let bitcode_path = build_script_dir_path.join("bitcode");
let src_obj_path = bitcode_path.join("builtins.o"); let src_obj_path = bitcode_path.join("builtins-64bit.o");
let src_obj = src_obj_path.to_str().expect("Invalid src object path"); let src_obj = src_obj_path.to_str().expect("Invalid src object path");
let dest_ir_path = bitcode_path.join("builtins.ll"); let dest_ir_path = bitcode_path.join("builtins-32bit.ll");
let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path"); let dest_ir_32bit = dest_ir_path.to_str().expect("Invalid dest ir path");
if use_build_script { let dest_ir_path = bitcode_path.join("builtins-64bit.ll");
println!("Compiling zig object & ir to: {} and {}", src_obj, dest_ir); let dest_ir_64bit = dest_ir_path.to_str().expect("Invalid dest ir path");
run_command_with_no_args(&bitcode_path, "./build.sh");
} else {
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
println!("Compiling ir to: {}", dest_ir); println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]); 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); println!("Moving zig object to: {}", dest_obj);
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]); run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
let dest_bc_path = bitcode_path.join("builtins.bc"); let dest_bc_path = bitcode_path.join("builtins-32bit.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path"); let dest_bc_32bit = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling bitcode to: {}", dest_bc); 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| { get_zig_files(bitcode_path.as_path(), &|path| {
let path: &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<()> { fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() { if dir.is_dir() {
for entry in fs::read_dir(dir)? { for entry in fs::read_dir(dir)? {

View file

@ -82,3 +82,4 @@ pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
pub const DEC_DIV: &str = "roc_builtins.dec.div"; pub const DEC_DIV: &str = "roc_builtins.dec.div";
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic"; pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";

View file

@ -985,11 +985,11 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Dict module // Dict module
// Dict.hashTestOnly : Nat, v -> Nat // Dict.hashTestOnly : U64, v -> U64
add_top_level_function_type!( add_top_level_function_type!(
Symbol::DICT_TEST_HASH, Symbol::DICT_TEST_HASH,
vec![u64_type(), flex(TVAR2)], vec![u64_type(), flex(TVAR2)],
Box::new(nat_type()) Box::new(u64_type())
); );
// len : Dict * * -> Nat // len : Dict * * -> Nat

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 { fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let int_precision_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 { Def {
annotation: None, 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 { fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let int_precision_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 { Def {
annotation: None, annotation: None,
@ -687,7 +687,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(unbound_zero_var, 0)), (arg_var, num(unbound_zero_var, 0)),
], ],
ret_var: bool_var, ret_var: bool_var,
}; };
@ -710,7 +710,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(arg_var, Num(unbound_zero_var, 0)), (arg_var, num(unbound_zero_var, 0)),
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -735,7 +735,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(unbound_zero_var, 0)), (arg_var, num(unbound_zero_var, 0)),
], ],
ret_var: bool_var, ret_var: bool_var,
}; };
@ -758,14 +758,14 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, Int(var_store.fresh(), var_store.fresh(), 1)), (arg_var, int(var_store.fresh(), var_store.fresh(), 1)),
( (
arg_var, arg_var,
RunLowLevel { RunLowLevel {
op: LowLevel::NumRemUnchecked, op: LowLevel::NumRemUnchecked,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(unbound_two_var, 2)), (arg_var, num(unbound_two_var, 2)),
], ],
ret_var: arg_var, ret_var: arg_var,
}, },
@ -792,14 +792,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, Num(arg_num_var, 0)), (arg_var, num(arg_num_var, 0)),
( (
arg_var, arg_var,
RunLowLevel { RunLowLevel {
op: LowLevel::NumRemUnchecked, op: LowLevel::NumRemUnchecked,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, Num(arg_num_var, 2)), (arg_var, num(arg_num_var, 2)),
], ],
ret_var: arg_var, ret_var: arg_var,
}, },
@ -853,7 +853,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGte, op: LowLevel::NumGte,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (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, ret_var: bool_var,
}), }),
@ -899,7 +899,7 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (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, 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 { fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let int_precision_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 std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap(); let solved = std.get(&symbol).unwrap();
@ -1166,13 +1166,13 @@ fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = Variable::NAT;
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = Variable::NATURAL;
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(len_var, Num(unbound_zero_var, 0)), (len_var, num(unbound_zero_var, 0)),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2051,7 +2051,7 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(closure_var, list_sum_add(num_var, var_store)), (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, ret_var,
}; };
@ -2093,7 +2093,7 @@ fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(closure_var, list_product_mul(num_var, var_store)), (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, ret_var,
}; };
@ -2198,7 +2198,22 @@ fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Dict.len : Dict * * -> Nat /// Dict.len : Dict * * -> Nat
fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def { fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::DictSize, var_store) let arg1_var = var_store.fresh();
let ret_var = Variable::NAT;
let body = RunLowLevel {
op: LowLevel::DictSize,
args: vec![(arg1_var, Var(Symbol::ARG_1))],
ret_var,
};
defn(
symbol,
vec![(arg1_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
} }
/// Dict.empty : Dict * * /// Dict.empty : Dict * *
@ -2571,7 +2586,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
(num_var, Num(unbound_zero_var, 0)), (num_var, num(unbound_zero_var, 0)),
], ],
ret_var: bool_var, ret_var: bool_var,
}, },
@ -2674,7 +2689,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (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, ret_var: bool_var,
}, },
@ -2739,7 +2754,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
( (
num_var, num_var,
Int(unbound_zero_var, unbound_zero_precision_var, 0), int(unbound_zero_var, unbound_zero_precision_var, 0),
), ),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -2792,9 +2807,9 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = Variable::NAT;
let zero_var = var_store.fresh(); let zero_var = len_var;
let zero_precision_var = var_store.fresh(); let zero_precision_var = Variable::NATURAL;
let list_elem_var = var_store.fresh(); let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
@ -2809,7 +2824,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, Int(zero_var, zero_precision_var, 0)), (len_var, int(zero_var, zero_precision_var, 0)),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2833,7 +2848,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (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, ret_var: list_elem_var,
}, },
@ -2873,9 +2888,9 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_var = var_store.fresh(); let arg_var = var_store.fresh();
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = Variable::NAT;
let num_var = var_store.fresh(); let num_var = len_var;
let num_precision_var = var_store.fresh(); let num_precision_var = Variable::NATURAL;
let list_elem_var = var_store.fresh(); let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
@ -2890,7 +2905,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, Int(num_var, num_precision_var, 0)), (len_var, int(num_var, num_precision_var, 0)),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2929,7 +2944,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var, 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, ret_var: len_var,
}, },
@ -3403,7 +3418,7 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level
add_var, add_var,
RunLowLevel { RunLowLevel {
ret_var: cast_var, 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, op: LowLevel::NumIntCast,
}, },
), ),
@ -3489,3 +3504,18 @@ fn defn_help(
loc_body: Box::new(no_region(body)), 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(_, _) NumLiteral(_, _, _)
| IntLiteral(_, _) | IntLiteral(_, _, _)
| FloatLiteral(_, _) | FloatLiteral(_, _, _)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -56,9 +56,9 @@ fn headers_from_annotation_help(
| Shadowed(_, _) | Shadowed(_, _)
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| NumLiteral(_, _) | NumLiteral(_, _, _)
| IntLiteral(_, _) | IntLiteral(_, _, _)
| FloatLiteral(_, _) | FloatLiteral(_, _, _)
| StrLiteral(_) => true, | StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -143,7 +143,7 @@ pub fn constrain_pattern(
); );
} }
NumLiteral(var, _) => { NumLiteral(var, _, _) => {
state.vars.push(*var); state.vars.push(*var);
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
@ -154,7 +154,7 @@ pub fn constrain_pattern(
)); ));
} }
IntLiteral(precision_var, _) => { IntLiteral(precision_var, _, _) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Int, PatternCategory::Int,
@ -163,7 +163,7 @@ pub fn constrain_pattern(
)); ));
} }
FloatLiteral(precision_var, _) => { FloatLiteral(precision_var, _, _) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Float, 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 = "14" # im and im-rc should always have the same version!
im-rc = "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"] } bumpalo = { version = "3.6.1", features = ["collections"] }
target-lexicon = "0.10" target-lexicon = "0.12.2"
libloading = "0.6" libloading = "0.6"
object = { version = "0.24", features = ["write"] } object = { version = "0.24", features = ["write"] }

View file

@ -228,6 +228,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
fn load_args<'a>( fn load_args<'a>(
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>, _symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)], _args: &'a [(Layout<'a>, Symbol)],
_ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
Err("Loading args not yet implemented for AArch64".to_string()) Err("Loading args not yet implemented for AArch64".to_string())
} }
@ -242,6 +243,20 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
) -> Result<u32, String> { ) -> Result<u32, String> {
Err("Storing args not yet implemented for AArch64".to_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 { impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {

View file

@ -10,6 +10,8 @@ use target_lexicon::Triple;
pub mod aarch64; pub mod aarch64;
pub mod x86_64; pub mod x86_64;
const PTR_SIZE: u32 = 64;
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> { pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
const GENERAL_PARAM_REGS: &'static [GeneralReg]; const GENERAL_PARAM_REGS: &'static [GeneralReg];
const GENERAL_RETURN_REGS: &'static [GeneralReg]; const GENERAL_RETURN_REGS: &'static [GeneralReg];
@ -49,6 +51,8 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn load_args<'a>( fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], 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>; ) -> Result<(), String>;
// store_args stores the args in registers and on the stack for function calling. // 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 is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<u32, String>; ) -> 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. /// Assembler contains calls to the backend assembly generator.
@ -160,8 +177,6 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> { 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), GeneralReg(GeneralReg),
FloatReg(FloatReg), FloatReg(FloatReg),
Base(i32), Base(i32),
@ -186,7 +201,7 @@ pub struct Backend64Bit<
last_seen_map: MutMap<Symbol, *const Stmt<'a>>, last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, 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>>, literal_map: MutMap<Symbol, Literal<'a>>,
// This should probably be smarter than a vec. // This should probably be smarter than a vec.
@ -226,7 +241,7 @@ impl<
relocs: bumpalo::vec!(in env.arena), relocs: bumpalo::vec!(in env.arena),
last_seen_map: MutMap::default(), last_seen_map: MutMap::default(),
free_map: MutMap::default(), free_map: MutMap::default(),
symbols_map: MutMap::default(), symbol_storage_map: MutMap::default(),
literal_map: MutMap::default(), literal_map: MutMap::default(),
general_free_regs: bumpalo::vec![in env.arena], general_free_regs: bumpalo::vec![in env.arena],
general_used_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.fn_call_stack_size = 0;
self.last_seen_map.clear(); self.last_seen_map.clear();
self.free_map.clear(); self.free_map.clear();
self.symbols_map.clear(); self.symbol_storage_map.clear();
self.buf.clear(); self.buf.clear();
self.general_used_callee_saved_regs.clear(); self.general_used_callee_saved_regs.clear();
self.general_free_regs.clear(); self.general_free_regs.clear();
@ -324,10 +339,14 @@ impl<
Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
} }
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String> { fn load_args(
CC::load_args(&mut self.symbols_map, 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. // Update used and free regs.
for (sym, storage) in &self.symbols_map { for (sym, storage) in &self.symbol_storage_map {
match storage { match storage {
SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => { SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => {
self.general_free_regs.retain(|r| *r != *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. // Put values in param regs or on top of the stack.
let tmp_stack_size = CC::store_args( let tmp_stack_size = CC::store_args(
&mut self.buf, &mut self.buf,
&self.symbols_map, &self.symbol_storage_map,
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -421,7 +440,7 @@ impl<
_cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations. _cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations.
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
_ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
// Switches are a little complex due to keeping track of jumps. // 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. // 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); let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Build all statements in this branch. // 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. // Build unconditional jump to the end of this switch.
// Since we don't know the offset yet, set it to 0 and overwrite later. // 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; let (branch_info, stmt) = default_branch;
if let BranchInfo::None = branch_info { 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. // Update all return jumps to jump past the default case.
let ret_offset = self.buf.len(); let ret_offset = self.buf.len();
@ -560,6 +579,61 @@ impl<
Ok(()) 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> { fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> {
match lit { match lit {
Literal::Int(x) => { Literal::Int(x) => {
@ -579,7 +653,7 @@ impl<
} }
fn free_symbol(&mut self, sym: &Symbol) { 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() { for i in 0..self.general_used_regs.len() {
let (reg, saved_sym) = self.general_used_regs[i]; let (reg, saved_sym) = self.general_used_regs[i];
if saved_sym == *sym { if saved_sym == *sym {
@ -590,8 +664,8 @@ impl<
} }
} }
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> { fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> {
let val = self.symbols_map.get(sym); let val = self.symbol_storage_map.get(sym);
match val { match val {
Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()), Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()),
Some(SymbolStorage::GeneralReg(reg)) => { Some(SymbolStorage::GeneralReg(reg)) => {
@ -605,6 +679,48 @@ impl<
ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg);
Ok(()) 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!( Some(x) => Err(format!(
"returning symbol storage, {:?}, is not yet implemented", "returning symbol storage, {:?}, is not yet implemented",
x x
@ -624,6 +740,25 @@ impl<
CC: CallConv<GeneralReg, FloatReg>, CC: CallConv<GeneralReg, FloatReg>,
> Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> > 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> { fn claim_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> {
let reg = if !self.general_free_regs.is_empty() { let reg = if !self.general_free_regs.is_empty() {
let free_reg = self.general_free_regs.pop().unwrap(); let free_reg = self.general_free_regs.pop().unwrap();
@ -640,7 +775,7 @@ impl<
}?; }?;
self.general_used_regs.push((reg, *sym)); self.general_used_regs.push((reg, *sym));
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::GeneralReg(reg)); .insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg) Ok(reg)
} }
@ -661,27 +796,28 @@ impl<
}?; }?;
self.float_used_regs.push((reg, *sym)); 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) Ok(reg)
} }
fn load_to_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> { 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 { match val {
Some(SymbolStorage::GeneralReg(reg)) => { Some(SymbolStorage::GeneralReg(reg)) => {
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::GeneralReg(reg)); .insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::Base(offset)) => { Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_general_reg(sym)?; let reg = self.claim_general_reg(sym)?;
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32); ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => { Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => {
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
Ok(reg) Ok(reg)
} }
@ -693,21 +829,22 @@ impl<
} }
fn load_to_float_reg(&mut self, sym: &Symbol) -> Result<FloatReg, String> { 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 { match val {
Some(SymbolStorage::FloatReg(reg)) => { Some(SymbolStorage::FloatReg(reg)) => {
self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg)); self.symbol_storage_map
.insert(*sym, SymbolStorage::FloatReg(reg));
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::Base(offset)) => { Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_float_reg(sym)?; let reg = self.claim_float_reg(sym)?;
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32); ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => { Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => {
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
Ok(reg) Ok(reg)
} }
@ -719,54 +856,94 @@ impl<
} }
fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> { 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 { match val {
Some(SymbolStorage::GeneralReg(reg)) => { 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. // For base addresssing, use the negative offset - 8.
ASM::mov_base32_reg64(&mut self.buf, -offset - 8, reg); ASM::mov_base32_reg64(&mut self.buf, offset, reg);
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(-offset - 8)); .insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::FloatReg(reg)) => { 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. // For base addresssing, use the negative offset.
ASM::mov_base32_freg64(&mut self.buf, -offset - 8, reg); ASM::mov_base32_freg64(&mut self.buf, offset, reg);
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(-offset - 8)); .insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::Base(offset)) => { Some(SymbolStorage::Base(offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => { Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::BaseAndFloatReg(_, offset)) => { Some(SymbolStorage::BaseAndFloatReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
None => Err(format!("Unknown symbol: {}", sym)), None => Err(format!("Unknown symbol: {}", sym)),
} }
} }
/// increase_stack_size increase the current stack size and returns the offset of the stack. /// increase_stack_size increase the current stack size `amount` bytes.
fn increase_stack_size(&mut self, amount: u32) -> Result<u32, String> { /// 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); debug_assert!(amount > 0);
let offset = self.stack_size;
if let Some(new_size) = self.stack_size.checked_add(amount) { 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. // 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 { if new_size > i32::MAX as u32 {
Err("Ran out of stack space".to_string()) Err("Ran out of stack space".to_string())
} else { } else {
self.stack_size = new_size; self.stack_size = new_size;
let offset = -(self.stack_size as i32);
Ok(offset) Ok(offset)
} }
} else { } else {
Err("Ran out of stack space".to_string()) 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 crate::Relocation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -177,10 +177,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
fn load_args<'a>( fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. 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 general_i = 0;
let mut float_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() { for (layout, sym) in args.iter() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
@ -359,6 +367,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
} }
Ok(stack_offset as u32) 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 { 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>( fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. 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() { if i < Self::GENERAL_PARAM_REGS.len() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
@ -496,6 +529,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
)); ));
} }
} }
i += 1;
} else { } else {
base_offset += match layout { base_offset += match layout {
Layout::Builtin(Builtin::Int64) => 8, Layout::Builtin(Builtin::Int64) => 8,
@ -653,6 +687,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
} }
Ok(stack_offset as u32) 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)] #[inline(always)]

View file

@ -66,7 +66,11 @@ where
// load_args is used to let the backend know what the args are. // 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. // 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 /// Used for generating wrappers for malloc/realloc/free
fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>; fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>;
@ -74,29 +78,29 @@ where
/// build_proc creates a procedure and outputs it to the wrapped object writer. /// 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> { fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset(); self.reset();
self.load_args(proc.args)?; self.load_args(proc.args, &proc.ret_layout)?;
// let start = std::time::Instant::now(); // let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
// let duration = start.elapsed(); // let duration = start.elapsed();
// println!("Time to calculate lifetimes: {:?}", duration); // println!("Time to calculate lifetimes: {:?}", duration);
// println!("{:?}", self.last_seen_map()); // println!("{:?}", self.last_seen_map());
self.build_stmt(&proc.body)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize() self.finalize()
} }
/// build_stmt builds a statement and outputs at the end of the buffer. /// 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 { match stmt {
Stmt::Let(sym, expr, layout, following) => { Stmt::Let(sym, expr, layout, following) => {
self.build_expr(sym, expr, layout)?; self.build_expr(sym, expr, layout)?;
self.free_symbols(stmt); self.free_symbols(stmt);
self.build_stmt(following)?; self.build_stmt(following, ret_layout)?;
Ok(()) Ok(())
} }
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
self.load_literal_symbols(&[*sym])?; self.load_literal_symbols(&[*sym])?;
self.return_symbol(sym)?; self.return_symbol(sym, ret_layout)?;
self.free_symbols(stmt); self.free_symbols(stmt);
Ok(()) Ok(())
} }
@ -218,6 +222,15 @@ where
x => Err(format!("the call type, {:?}, is not yet implemented", x)), 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)), x => Err(format!("the expression, {:?}, is not yet implemented", x)),
} }
} }
@ -377,11 +390,28 @@ where
Ok(()) 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. /// load_literal sets a symbol to be equal to a literal.
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>; 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. /// 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. /// free_symbols will free all symbols for the given statement.
fn free_symbols(&mut self, stmt: &Stmt<'a>) { fn free_symbols(&mut self, stmt: &Stmt<'a>) {

View file

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

View file

@ -16,12 +16,13 @@ roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_std = { path = "../../roc_std" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }
im = "14" # im and im-rc should always have the same version! 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! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inkwell = { path = "../../vendor/inkwell" } inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10" target-lexicon = "0.12.2"
[dev-dependencies] [dev-dependencies]
roc_can = { path = "../can" } roc_can = { path = "../can" }
@ -29,7 +30,6 @@ roc_parse = { path = "../parse" }
roc_load = { path = "../load" } roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" } roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"

View file

@ -1,3 +1,4 @@
use std::convert::TryFrom;
use std::path::Path; use std::path::Path;
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; 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> { 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> { pub fn ptr_int(&self) -> IntType<'ctx> {
ptr_int(self.context, self.ptr_bytes) 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 { pub fn small_str_bytes(&self) -> u32 {
self.ptr_bytes * 2 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, // In the build script for the builtins module,
// we compile the builtins into LLVM bitcode // 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); 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, precision: &Builtin,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
match precision { 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::Float64 => env.context.f64_type().const_float(value).into(),
Builtin::Float32 => env.context.f32_type().const_float(value).into(), Builtin::Float32 => env.context.f32_type().const_float(value).into(),
_ => panic!("Invalid layout for float literal = {:?}", precision), _ => 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), _ => 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(), 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(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => { Str(str_literal) => {
@ -927,11 +948,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
CallType::Foreign { CallType::Foreign {
foreign_symbol, foreign_symbol,
ret_layout, ret_layout,
} => { } => build_foreign_symbol(env, scope, foreign_symbol, arguments, 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)
}
} }
} }
@ -1689,7 +1706,11 @@ pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
pointer: PointerValue<'ctx>, pointer: PointerValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let mask: u64 = 0b0000_0111; let mask: u64 = match env.ptr_bytes {
8 => 0b0000_0111,
4 => 0b0000_0011,
_ => unreachable!(),
};
let ptr_int = env.ptr_int(); let ptr_int = env.ptr_int();
@ -1708,11 +1729,17 @@ pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>(
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let ptr_int = env.ptr_int(); let ptr_int = env.ptr_int();
let tag_id_bits_mask = match env.ptr_bytes {
8 => 3,
4 => 2,
_ => unreachable!(),
};
let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int");
let mask = { let mask = {
let a = env.ptr_int().const_all_ones(); let a = env.ptr_int().const_all_ones();
let tag_id_bits = env.ptr_int().const_int(3, false); let tag_id_bits = env.ptr_int().const_int(tag_id_bits_mask, false);
env.builder.build_left_shift(a, tag_id_bits, "make_mask") env.builder.build_left_shift(a, tag_id_bits, "make_mask")
}; };
@ -3085,21 +3112,6 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
.into_pointer_value() .into_pointer_value()
} }
pub fn get_sjlj_message_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let type_ = env.context.i8_type().ptr_type(AddressSpace::Generic);
let global = match env.module.get_global("roc_sjlj_message_buffer") {
Some(global) => global,
None => env
.module
.add_global(type_, None, "roc_sjlj_message_buffer"),
};
global.set_initializer(&type_.const_zero());
global.as_pointer_value()
}
fn set_jump_and_catch_long_jump<'a, 'ctx, 'env, F, T>( fn set_jump_and_catch_long_jump<'a, 'ctx, 'env, F, T>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
@ -3931,6 +3943,8 @@ pub fn get_call_conventions(cc: target_lexicon::CallingConvention) -> u32 {
SystemV => C_CALL_CONV, SystemV => C_CALL_CONV,
WasmBasicCAbi => C_CALL_CONV, WasmBasicCAbi => C_CALL_CONV,
WindowsFastcall => C_CALL_CONV, WindowsFastcall => C_CALL_CONV,
AppleAarch64 => C_CALL_CONV,
_ => C_CALL_CONV,
} }
} }
@ -4429,6 +4443,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)] #[allow(clippy::too_many_arguments)]
fn run_low_level<'a, 'ctx, 'env>( fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -4439,6 +4458,7 @@ fn run_low_level<'a, 'ctx, 'env>(
op: LowLevel, op: LowLevel,
args: &[Symbol], args: &[Symbol],
update_mode: Option<UpdateMode>, update_mode: Option<UpdateMode>,
// expect_failed: *const (),
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use LowLevel::*; use LowLevel::*;
@ -4713,7 +4733,7 @@ fn run_low_level<'a, 'ctx, 'env>(
complex_bitcast( complex_bitcast(
env.builder, env.builder,
list.into(), list.into(),
env.context.i128_type().into(), env.str_list_c_abi().into(),
"to_i128", "to_i128",
), ),
position, position,
@ -4731,7 +4751,7 @@ fn run_low_level<'a, 'ctx, 'env>(
complex_bitcast( complex_bitcast(
env.builder, env.builder,
list.into(), list.into(),
env.context.i128_type().into(), env.str_list_c_abi().into(),
"to_i128", "to_i128",
), ),
position, position,
@ -5155,9 +5175,36 @@ fn run_low_level<'a, 'ctx, 'env>(
bd.build_conditional_branch(condition, then_block, throw_block); bd.build_conditional_branch(condition, then_block, throw_block);
bd.position_at_end(throw_block); {
bd.position_at_end(throw_block);
throw_exception(env, "assert failed!"); match env.ptr_bytes {
8 => {
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);
}
4 => {
// temporary WASM implementation
throw_exception(env, "An expectation failed!");
}
_ => unreachable!(),
}
}
bd.position_at_end(then_block); bd.position_at_end(then_block);
@ -5170,89 +5217,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>, env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>, layout: &Layout<'a>,
foreign: &roc_module::ident::ForeignSymbol, ) -> BasicTypeEnum<'ctx> {
arguments: &[Symbol], match layout {
return_type: BasicTypeEnum<'ctx>, Layout::Builtin(builtin) => to_cc_type_builtin(env, builtin),
) -> (FunctionValue<'ctx>, &'a [BasicValueEnum<'ctx>]) { _ => {
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena); // TODO this is almost certainly incorrect for bigger structs
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena); basic_type_from_layout(env, layout)
}
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);
} }
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>, env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>, builtin: &Builtin<'a>,
foreign: &roc_module::ident::ForeignSymbol, ) -> BasicTypeEnum<'ctx> {
arguments: &[Symbol], match builtin {
return_pointer: PointerValue<'ctx>, Builtin::Int128
) -> (FunctionValue<'ctx>, &'a [BasicValueEnum<'ctx>]) { | Builtin::Int64
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena); | Builtin::Int32
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena); | Builtin::Int16
| Builtin::Int8
arg_vals.push(return_pointer.into()); | Builtin::Int1
arg_types.push(return_pointer.get_type().into()); | Builtin::Usize
| Builtin::Decimal
for arg in arguments.iter() { | Builtin::Float128
let (value, layout) = load_symbol_and_layout(scope, arg); | Builtin::Float64
arg_vals.push(value); | Builtin::Float32
let arg_type = basic_type_from_layout(env, layout); | Builtin::Float16 => basic_type_from_builtin(env, builtin),
debug_assert_eq!(arg_type, value.get_type()); Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => {
arg_types.push(arg_type); 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>( fn build_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol, foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol], argument_symbols: &[Symbol],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let ret_type = basic_type_from_layout(env, ret_layout); let builder = env.builder;
let return_pointer = env.builder.build_alloca(ret_type, "return_value"); let context = env.context;
// crude approximation of the C calling convention // Here we build two functions:
let pass_result_by_pointer = ret_layout.stack_size(env.ptr_bytes) > 2 * env.ptr_bytes; //
// - 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 { let return_type = basic_type_from_layout(env, ret_layout);
build_foreign_symbol_write_result_into_ptr(env, scope, foreign, arguments, return_pointer) let cc_return = to_cc_return(env, ret_layout);
} else {
build_foreign_symbol_return_result(env, scope, foreign, arguments, ret_type) 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 let fastcc_type = return_type.fn_type(&fastcc_argument_types, false);
call.set_call_convention(C_CALL_CONV);
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 { let old = builder.get_insert_block().unwrap();
env.builder.build_load(return_pointer, "read_result")
} else { let entry = context.append_basic_block(fastcc_function, "entry");
call.try_as_basic_value().left().unwrap() {
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>( fn throw_on_overflow<'a, 'ctx, 'env>(
@ -5928,7 +6057,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]), NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]), NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]),
NumLogUnchecked => env.call_intrinsic(LLVM_LOG_F64, &[arg.into()]), NumLogUnchecked => env.call_intrinsic(LLVM_LOG_F64, &[arg.into()]),
NumRound => env.call_intrinsic(LLVM_LROUND_I64_F64, &[arg.into()]), NumRound => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ROUND),
NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]), NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]),
NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]), NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]),
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */ NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */

View file

@ -6,42 +6,39 @@ use crate::llvm::build::{
complex_bitcast, load_symbol, load_symbol_and_layout, Env, RocFunctionCall, Scope, complex_bitcast, load_symbol, load_symbol_and_layout, Env, RocFunctionCall, Scope,
}; };
use crate::llvm::build_list::{layout_width, pass_as_opaque}; 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 crate::llvm::refcounting::Mode;
use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::context::Context;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, StructValue}; use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, StructValue};
use inkwell::AddressSpace; use inkwell::AddressSpace;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutIds};
#[repr(u8)] #[repr(transparent)]
enum Alignment { struct Alignment(u8);
Align16KeyFirst = 0,
Align16ValueFirst = 1,
Align8KeyFirst = 2,
Align8ValueFirst = 3,
}
impl Alignment { impl Alignment {
fn from_key_value_layout(key: &Layout, value: &Layout, ptr_bytes: u32) -> Alignment { fn from_key_value_layout(key: &Layout, value: &Layout, ptr_bytes: u32) -> Alignment {
let key_align = key.alignment_bytes(ptr_bytes); let key_align = key.alignment_bytes(ptr_bytes);
let value_align = value.alignment_bytes(ptr_bytes); let value_align = value.alignment_bytes(ptr_bytes);
if key_align >= value_align { let mut bits = key_align.max(value_align) as u8;
match key_align.max(value_align) { debug_assert!(bits == 4 || bits == 8 || bits == 16);
8 => Alignment::Align8KeyFirst,
16 => Alignment::Align16KeyFirst, let value_before_key_flag = 0b1000_0000;
_ => unreachable!(),
} if key_align < value_align {
} else { bits |= value_before_key_flag;
match key_align.max(value_align) {
8 => Alignment::Align8ValueFirst,
16 => Alignment::Align16ValueFirst,
_ => unreachable!(),
}
} }
Alignment(bits)
}
fn as_int_value<'ctx>(&self, context: &'ctx Context) -> IntValue<'ctx> {
context.i8_type().const_int(self.0 as u64, false)
} }
} }
@ -50,8 +47,6 @@ pub fn dict_len<'a, 'ctx, 'env>(
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
dict_symbol: Symbol, dict_symbol: Symbol,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let (_, dict_layout) = load_symbol_and_layout(scope, &dict_symbol); let (_, dict_layout) = load_symbol_and_layout(scope, &dict_symbol);
match dict_layout { match dict_layout {
@ -59,14 +54,17 @@ pub fn dict_len<'a, 'ctx, 'env>(
// let dict_as_int = dict_symbol_to_i128(env, scope, dict_symbol); // 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_as_zig_dict = dict_symbol_to_zig_dict(env, scope, dict_symbol);
let dict_ptr = env let length_i64 = call_bitcode_fn(
.builder env,
.build_alloca(dict_as_zig_dict.get_type(), "dict_ptr"); &[pass_dict_c_abi(env, dict_as_zig_dict.into())],
env.builder.build_store(dict_ptr, dict_as_zig_dict); bitcode::DICT_LEN,
);
call_bitcode_fn(env, &[dict_ptr.into()], bitcode::DICT_LEN) env.builder
.build_int_cast(length_i64.into_int_value(), env.ptr_int(), "to_usize")
.into()
} }
Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(), Layout::Builtin(Builtin::EmptyDict) => env.ptr_int().const_zero().into(),
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout), _ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout),
} }
} }
@ -95,14 +93,11 @@ pub fn dict_insert<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; 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 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 key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
let value_ptr = builder.build_alloca(value.get_type(), "value_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(key_ptr, key);
env.builder.build_store(value_ptr, value); env.builder.build_store(value_ptr, value);
@ -114,10 +109,10 @@ pub fn dict_insert<'a, 'ctx, 'env>(
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -128,7 +123,7 @@ pub fn dict_insert<'a, 'ctx, 'env>(
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
alignment_iv.into(), alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(), key_width.into(),
@ -157,13 +152,10 @@ pub fn dict_remove<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; 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 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 key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(dict_ptr, dict);
env.builder.build_store(key_ptr, key); env.builder.build_store(key_ptr, key);
let key_width = env let key_width = env
@ -174,10 +166,10 @@ pub fn dict_remove<'a, 'ctx, 'env>(
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -188,7 +180,7 @@ pub fn dict_remove<'a, 'ctx, 'env>(
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
alignment_iv.into(), alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(), key_width.into(),
@ -216,13 +208,10 @@ pub fn dict_contains<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; 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 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 key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(dict_ptr, dict);
env.builder.build_store(key_ptr, key); env.builder.build_store(key_ptr, key);
let key_width = env let key_width = env
@ -234,7 +223,7 @@ pub fn dict_contains<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -242,7 +231,7 @@ pub fn dict_contains<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
alignment_iv.into(), alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(), key_width.into(),
@ -265,13 +254,10 @@ pub fn dict_get<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; 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 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 key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(dict_ptr, dict);
env.builder.build_store(key_ptr, key); env.builder.build_store(key_ptr, key);
let key_width = env let key_width = env
@ -283,7 +269,7 @@ pub fn dict_get<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -294,7 +280,7 @@ pub fn dict_get<'a, 'ctx, 'env>(
let result = call_bitcode_fn( let result = call_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
alignment_iv.into(), alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(), key_width.into(),
@ -376,13 +362,6 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
value_layout: &Layout<'a>, value_layout: &Layout<'a>,
rc_operation: Mode, 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 let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -392,7 +371,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 { let (key_fn, value_fn) = match rc_operation {
Mode::Inc => ( Mode::Inc => (
@ -408,7 +387,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
alignment_iv.into(), alignment_iv.into(),
key_width.into(), key_width.into(),
value_width.into(), value_width.into(),
@ -429,12 +408,6 @@ pub fn dict_keys<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; 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 let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -444,16 +417,16 @@ pub fn dict_keys<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 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( call_void_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
alignment_iv.into(), alignment_iv.into(),
key_width.into(), key_width.into(),
value_width.into(), value_width.into(),
@ -475,6 +448,26 @@ pub fn dict_keys<'a, 'ctx, 'env>(
env.builder.build_load(list_ptr, "load_keys_list") 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)] #[allow(clippy::too_many_arguments)]
pub fn dict_union<'a, 'ctx, 'env>( pub fn dict_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -486,14 +479,6 @@ pub fn dict_union<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; 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 let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -503,7 +488,7 @@ pub fn dict_union<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -511,13 +496,13 @@ pub fn dict_union<'a, 'ctx, 'env>(
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout); 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 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( call_void_bitcode_fn(
env, env,
&[ &[
dict1_ptr.into(), pass_dict_c_abi(env, dict1),
dict2_ptr.into(), pass_dict_c_abi(env, dict2),
alignment_iv.into(), alignment_iv.into(),
key_width.into(), key_width.into(),
value_width.into(), value_width.into(),
@ -587,12 +572,6 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); 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 let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -602,7 +581,7 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -615,8 +594,8 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
dict1_ptr.into(), pass_dict_c_abi(env, dict1),
dict2_ptr.into(), pass_dict_c_abi(env, dict2),
alignment_iv.into(), alignment_iv.into(),
key_width.into(), key_width.into(),
value_width.into(), value_width.into(),
@ -645,24 +624,20 @@ pub fn dict_walk<'a, 'ctx, 'env>(
let builder = env.builder; let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); 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_bt = basic_type_from_layout(env, accum_layout);
let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr"); let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr");
env.builder.build_store(accum_ptr, accum); env.builder.build_store(accum_ptr, accum);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); 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"); let output_ptr = builder.build_alloca(accum_bt, "output_ptr");
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
roc_function_call.caller.into(), roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
@ -690,12 +665,8 @@ pub fn dict_values<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; 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 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 let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
@ -705,7 +676,7 @@ pub fn dict_values<'a, 'ctx, 'env>(
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .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 = 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); let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
@ -714,7 +685,7 @@ pub fn dict_values<'a, 'ctx, 'env>(
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
dict_ptr.into(), pass_dict_c_abi(env, dict),
alignment_iv.into(), alignment_iv.into(),
key_width.into(), key_width.into(),
value_width.into(), value_width.into(),
@ -748,7 +719,7 @@ pub fn set_from_list<'a, 'ctx, 'env>(
let list_alloca = builder.build_alloca(list.get_type(), "list_alloca"); let list_alloca = builder.build_alloca(list.get_type(), "list_alloca");
let list_ptr = env.builder.build_bitcast( let list_ptr = env.builder.build_bitcast(
list_alloca, list_alloca,
env.context.i128_type().ptr_type(AddressSpace::Generic), env.str_list_c_abi().ptr_type(AddressSpace::Generic),
"to_zig_list", "to_zig_list",
); );
@ -764,7 +735,7 @@ pub fn set_from_list<'a, 'ctx, 'env>(
let alignment = let alignment =
Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.ptr_bytes); 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
@ -865,11 +836,11 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
) -> StructValue<'ctx> { ) -> StructValue<'ctx> {
let dict = load_symbol(scope, &symbol); let dict = load_symbol(scope, &symbol);
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); complex_bitcast(
env.builder,
complex_bitcast(env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict").into_struct_value() dict,
} crate::llvm::convert::zig_dict_type(env).into(),
"dict_to_zig_dict",
fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> { )
env.module.get_struct_type("dict.RocDict").unwrap() .into_struct_value()
} }

View file

@ -133,7 +133,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
// let zig deal with big vs small string // let zig deal with big vs small string
call_bitcode_fn( call_bitcode_fn(
env, 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, bitcode::DICT_HASH_STR,
) )
.into_int_value() .into_int_value()
@ -785,15 +785,7 @@ fn hash_list<'a, 'ctx, 'env>(
env.builder.build_store(result, answer); env.builder.build_store(result, answer);
}; };
incrementing_elem_loop( incrementing_elem_loop(env, parent, ptr, length, "current_index", loop_fn);
env.builder,
env.context,
parent,
ptr,
length,
"current_index",
loop_fn,
);
env.builder.build_unconditional_branch(done_block); env.builder.build_unconditional_branch(done_block);
@ -886,7 +878,7 @@ fn hash_bitcode_fn<'a, 'ctx, 'env>(
buffer: PointerValue<'ctx>, buffer: PointerValue<'ctx>,
width: u32, width: u32,
) -> IntValue<'ctx> { ) -> 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( call_bitcode_fn(
env, env,

View file

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

View file

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

View file

@ -465,8 +465,8 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
let end = len1; let end = len1;
// allocate a stack slot for the current index // allocate a stack slot for the current index
let index_alloca = builder.build_alloca(ctx.i64_type(), "index"); let index_alloca = builder.build_alloca(env.ptr_int(), "index");
builder.build_store(index_alloca, ctx.i64_type().const_zero()); builder.build_store(index_alloca, env.ptr_int().const_zero());
let loop_bb = ctx.append_basic_block(parent, "loop"); let loop_bb = ctx.append_basic_block(parent, "loop");
let body_bb = ctx.append_basic_block(parent, "body"); 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); env.builder.position_at_end(increment_bb);
// constant 1i64 // constant 1isize
let one = ctx.i64_type().const_int(1, false); let one = env.ptr_int().const_int(1, false);
let next_index = builder.build_int_add(curr_index, one, "nextindex"); 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>( pub fn zig_dict_type<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>, env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
) -> StructType<'ctx> { ) -> StructType<'ctx> {

View file

@ -139,7 +139,9 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
} }
} }
add_sjlj_roc_panic(env) if env.is_gen_test {
add_sjlj_roc_panic(env)
}
} }
pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {

View file

@ -1,7 +1,8 @@
use crate::debug_info_init; use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{ use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive, 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, TAG_DATA_INDEX,
}; };
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list}; use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{basic_type_from_layout, ptr_int}; use crate::llvm::convert::{basic_type_from_layout, ptr_int};
@ -170,7 +171,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
None => { None => {
// inc and dec return void // inc and dec return void
let fn_type = context.void_type().fn_type( 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, false,
); );
@ -211,121 +212,28 @@ impl<'ctx> PointerToRefcount<'ctx> {
) { ) {
let builder = env.builder; let builder = env.builder;
let ctx = env.context; let ctx = env.context;
let refcount_type = ptr_int(ctx, env.ptr_bytes);
let entry = ctx.append_basic_block(parent, "entry"); let entry = ctx.append_basic_block(parent, "entry");
builder.position_at_end(entry); builder.position_at_end(entry);
debug_info_init!(env, parent); debug_info_init!(env, parent);
let refcount_ptr = { let alignment = env.context.i32_type().const_int(alignment as _, false);
let raw_refcount_ptr = parent.get_nth_param(0).unwrap();
debug_assert!(raw_refcount_ptr.is_pointer_value());
Self {
value: raw_refcount_ptr.into_pointer_value(),
}
};
let refcount = refcount_ptr.get_refcount(env); call_void_bitcode_fn(
env,
let is_static_allocation = builder.build_int_compare( &[
IntPredicate::EQ, env.builder.build_bitcast(
refcount, parent.get_nth_param(0).unwrap(),
env.ptr_int().const_zero(), env.ptr_int().ptr_type(AddressSpace::Generic),
"is_static_allocation", "foo",
),
alignment.into(),
],
roc_builtins::bitcode::UTILS_DECREF,
); );
// build blocks builder.build_return(None);
let branch_block = ctx.append_basic_block(parent, "branch");
let then_block = ctx.append_basic_block(parent, "then");
let else_block = ctx.append_basic_block(parent, "else");
let return_block = ctx.append_basic_block(parent, "return");
builder.build_conditional_branch(is_static_allocation, return_block, branch_block);
let add_with_overflow;
{
builder.position_at_end(branch_block);
add_with_overflow = env
.call_intrinsic(
LLVM_SADD_WITH_OVERFLOW_I64,
&[
refcount.into(),
refcount_type.const_int(-1_i64 as u64, true).into(),
],
)
.into_struct_value();
let has_overflowed = builder
.build_extract_value(add_with_overflow, 1, "has_overflowed")
.unwrap();
let has_overflowed_comparison = builder.build_int_compare(
IntPredicate::EQ,
has_overflowed.into_int_value(),
ctx.bool_type().const_int(1_u64, false),
"has_overflowed",
);
// TODO what would be most optimial for the branch predictor
//
// are most refcounts 1 most of the time? or not?
builder.build_conditional_branch(has_overflowed_comparison, then_block, else_block);
}
// build then block
{
builder.position_at_end(then_block);
if !env.is_gen_test {
let ptr = builder.build_pointer_cast(
refcount_ptr.value,
ctx.i8_type().ptr_type(AddressSpace::Generic),
"cast_to_i8_ptr",
);
match alignment {
n if env.ptr_bytes == n => {
// the refcount ptr is also the ptr to the allocated region
env.call_dealloc(ptr, alignment);
}
n if 2 * env.ptr_bytes == n => {
// we need to step back another ptr_bytes to get the allocated ptr
let allocated = Self::from_ptr_to_data(env, ptr);
env.call_dealloc(allocated.value, alignment);
}
n => unreachable!("invalid extra_bytes {:?}", n),
}
}
builder.build_unconditional_branch(return_block);
}
// build else block
{
builder.position_at_end(else_block);
let max = builder.build_int_compare(
IntPredicate::EQ,
refcount,
refcount_type.const_int(REFCOUNT_MAX as u64, false),
"refcount_max_check",
);
let decremented = builder
.build_extract_value(add_with_overflow, 0, "decrement_refcount")
.unwrap()
.into_int_value();
let selected = builder.build_select(max, refcount, decremented, "select_refcount");
refcount_ptr.set_refcount(env, selected.into_int_value());
builder.build_unconditional_branch(return_block);
}
{
builder.position_at_end(return_block);
builder.build_return(None);
}
} }
} }
@ -774,7 +682,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
let is_non_empty = builder.build_int_compare( let is_non_empty = builder.build_int_compare(
IntPredicate::UGT, IntPredicate::UGT,
len, len,
ctx.i64_type().const_zero(), env.ptr_int().const_zero(),
"len > 0", "len > 0",
); );
@ -803,15 +711,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
); );
}; };
incrementing_elem_loop( incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn);
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
} }
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper); let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);

View file

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

View file

@ -831,6 +831,9 @@ define_builtins! {
// used to initialize parameters in borrow.rs // used to initialize parameters in borrow.rs
22 EMPTY_PARAM: "#empty_param" 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" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 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_can = { path = "../can" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_std = { path = "../../roc_std" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }

View file

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

View file

@ -7,6 +7,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_std::RocDec;
/// COMPILE CASES /// COMPILE CASES
@ -85,8 +86,8 @@ enum Test<'a> {
arguments: Vec<(Pattern<'a>, Layout<'a>)>, arguments: Vec<(Pattern<'a>, Layout<'a>)>,
}, },
IsInt(i128), IsInt(i128),
// float patterns are stored as u64 so they are comparable/hashable
IsFloat(u64), IsFloat(u64),
IsDecimal(RocDec),
IsStr(Box<str>), IsStr(Box<str>),
IsBit(bool), IsBit(bool),
IsByte { IsByte {
@ -126,6 +127,11 @@ impl<'a> Hash for Test<'a> {
tag_id.hash(state); tag_id.hash(state);
num_alts.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::IsBit(_) => number_of_tests == 2,
Test::IsInt(_) => false, Test::IsInt(_) => false,
Test::IsFloat(_) => false, Test::IsFloat(_) => false,
Test::IsDecimal(_) => false,
Test::IsStr(_) => false, Test::IsStr(_) => false,
} }
} }
@ -556,6 +563,7 @@ fn test_at_path<'a>(
}, },
IntLiteral(v) => IsInt(*v), IntLiteral(v) => IsInt(*v),
FloatLiteral(v) => IsFloat(*v), FloatLiteral(v) => IsFloat(*v),
DecimalLiteral(v) => IsDecimal(*v),
StrLiteral(v) => IsStr(v.clone()), StrLiteral(v) => IsStr(v.clone()),
}; };
@ -823,6 +831,18 @@ fn to_relevant_branch_help<'a>(
_ => None, _ => 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 { BitLiteral { value: bit, .. } => match test {
IsBit(test_bit) if bit == *test_bit => { IsBit(test_bit) if bit == *test_bit => {
start.extend(end); start.extend(end);
@ -910,6 +930,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
| EnumLiteral { .. } | EnumLiteral { .. }
| IntLiteral(_) | IntLiteral(_)
| FloatLiteral(_) | FloatLiteral(_)
| DecimalLiteral(_)
| StrLiteral(_) => true, | StrLiteral(_) => true,
} }
} }
@ -1279,6 +1300,14 @@ fn test_to_equality<'a>(
(stores, lhs_symbol, rhs_symbol, None) (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 { Test::IsByte {
tag_id: test_byte, .. tag_id: test_byte, ..
} => { } => {

View file

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

View file

@ -14,6 +14,7 @@ use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError; use roc_problem::can::RuntimeError;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_std::RocDec;
use roc_types::solved_types::SolvedType; use roc_types::solved_types::SolvedType;
use roc_types::subs::{Content, FlatType, Subs, Variable, VariableSubsSlice}; use roc_types::subs::{Content, FlatType, Subs, Variable, VariableSubsSlice};
use std::collections::HashMap; use std::collections::HashMap;
@ -694,10 +695,8 @@ impl<'a> Procs<'a> {
layout: ProcLayout<'a>, layout: ProcLayout<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) { ) {
let tuple = (name, layout);
// If we've already specialized this one, no further work is needed. // 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; return;
} }
@ -707,15 +706,12 @@ impl<'a> Procs<'a> {
return; 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. // This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass! // Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations { match &mut self.pending_specializations {
Some(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 // register the pending specialization, so this gets code genned later
if self.module_thunks.contains(&name) { if self.module_thunks.contains(&name) {
debug_assert!(layout.arguments.is_empty()); debug_assert!(layout.arguments.is_empty());
@ -736,7 +732,26 @@ impl<'a> Procs<'a> {
// (We had a bug around this before this system existed!) // (We had a bug around this before this system existed!)
self.specialized.insert((symbol, layout), InProgress); 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)) => { Ok((proc, _ignore_layout)) => {
// the `layout` is a function pointer, while `_ignore_layout` can be a // the `layout` is a function pointer, while `_ignore_layout` can be a
// closure. We only specialize functions, storing this value with a closure // closure. We only specialize functions, storing this value with a closure
@ -1010,6 +1025,7 @@ pub enum Literal<'a> {
// Literals // Literals
Int(i128), Int(i128),
Float(f64), Float(f64),
Decimal(RocDec),
Str(&'a str), Str(&'a str),
/// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool, /// 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. /// so they can (at least potentially) be emitted as 1-bit machine bools.
@ -1191,6 +1207,8 @@ impl<'a> Literal<'a> {
match self { match self {
Int(lit) => alloc.text(format!("{}i64", lit)), Int(lit) => alloc.text(format!("{}i64", lit)),
Float(lit) => alloc.text(format!("{}f64", 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)), Bool(lit) => alloc.text(format!("{}", lit)),
Byte(lit) => alloc.text(format!("{}u8", lit)), Byte(lit) => alloc.text(format!("{}u8", lit)),
Str(lit) => alloc.text(format!("{:?}", lit)), Str(lit) => alloc.text(format!("{:?}", lit)),
@ -1688,7 +1706,7 @@ fn pattern_to_when<'a>(
(symbol, Located::at_zero(wrapped_body)) (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 // These patters are refutable, and thus should never occur outside a `when` expression
// They should have been replaced with `UnsupportedPattern` during canonicalization // They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
@ -2448,13 +2466,57 @@ fn specialize_solved_type<'a>(
host_exposed_aliases: BumpMap<Symbol, SolvedType>, host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>, partial_proc: PartialProc<'a>,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'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 // add the specializations that other modules require of us
use roc_solve::solve::instantiate_rigids; use roc_solve::solve::instantiate_rigids;
let snapshot = env.subs.snapshot(); let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.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 // for debugging only
let raw = layout_cache let raw = layout_cache
@ -2680,17 +2742,17 @@ pub fn with_hole<'a>(
let arena = env.arena; let arena = env.arena;
match can_expr { match can_expr {
Int(_, precision, num) => { Int(_, precision, _, int) => {
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, false) { match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, false) {
IntOrFloat::SignedIntType(precision) => Stmt::Let( IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(num)), Expr::Literal(Literal::Int(int)),
Layout::Builtin(int_precision_to_builtin(precision)), Layout::Builtin(int_precision_to_builtin(precision)),
hole, hole,
), ),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let( IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(num)), Expr::Literal(Literal::Int(int)),
Layout::Builtin(int_precision_to_builtin(precision)), Layout::Builtin(int_precision_to_builtin(precision)),
hole, hole,
), ),
@ -2698,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) { match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, true) {
IntOrFloat::BinaryFloatType(precision) => Stmt::Let( IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Float(num as f64)), Expr::Literal(Literal::Float(float)),
Layout::Builtin(float_precision_to_builtin(precision)), Layout::Builtin(float_precision_to_builtin(precision)),
hole, hole,
), ),
IntOrFloat::DecimalFloatType => Stmt::Let( IntOrFloat::DecimalFloatType => {
assigned, let dec = match RocDec::from_str(&float_str) {
Expr::Literal(Literal::Float(num as f64)), Some(d) => d,
Layout::Builtin(Builtin::Decimal), None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", float_str),
hole, };
), Stmt::Let(
assigned,
Expr::Literal(Literal::Decimal(dec)),
Layout::Builtin(Builtin::Decimal),
hole,
)
}
_ => unreachable!("unexpected float precision for integer"), _ => unreachable!("unexpected float precision for integer"),
} }
} }
@ -2723,32 +2791,41 @@ pub fn with_hole<'a>(
hole, hole,
), ),
Num(var, num) => match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) { Num(var, num_str, num) => {
IntOrFloat::SignedIntType(precision) => Stmt::Let( // first figure out what kind of number this is
assigned, match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) {
Expr::Literal(Literal::Int(num.into())), IntOrFloat::SignedIntType(precision) => Stmt::Let(
Layout::Builtin(int_precision_to_builtin(precision)), assigned,
hole, Expr::Literal(Literal::Int(num.into())),
), Layout::Builtin(int_precision_to_builtin(precision)),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let( hole,
assigned, ),
Expr::Literal(Literal::Int(num.into())), IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
Layout::Builtin(int_precision_to_builtin(precision)), assigned,
hole, Expr::Literal(Literal::Int(num.into())),
), Layout::Builtin(int_precision_to_builtin(precision)),
IntOrFloat::BinaryFloatType(precision) => Stmt::Let( hole,
assigned, ),
Expr::Literal(Literal::Float(num as f64)), IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
Layout::Builtin(float_precision_to_builtin(precision)), assigned,
hole, Expr::Literal(Literal::Float(num as f64)),
), Layout::Builtin(float_precision_to_builtin(precision)),
IntOrFloat::DecimalFloatType => Stmt::Let( hole,
assigned, ),
Expr::Literal(Literal::Float(num as f64)), IntOrFloat::DecimalFloatType => {
Layout::Builtin(Builtin::Decimal), let dec = match RocDec::from_str(&num_str) {
hole, 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, _) => { LetNonRec(def, cont, _) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure { if let Closure {
@ -3047,7 +3124,8 @@ pub fn with_hole<'a>(
mut fields, 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 field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
@ -3382,7 +3460,8 @@ pub fn with_hole<'a>(
loc_expr, 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 index = None;
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -3524,7 +3603,8 @@ pub fn with_hole<'a>(
// This has the benefit that we don't need to do anything special for reference // This has the benefit that we don't need to do anything special for reference
// counting // 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); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -4128,7 +4208,8 @@ fn convert_tag_union<'a>(
arena: &'a Bump, arena: &'a Bump,
) -> Stmt<'a> { ) -> Stmt<'a> {
use crate::layout::UnionVariant::*; 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 { let variant = match res_variant {
Ok(cached) => cached, Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => { Err(LayoutProblem::UnresolvedTypeVar(_)) => {
@ -4464,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); let symbol = possible_reuse_symbol(env, procs, &arg.value);
field_symbols_temp.push((alignment, symbol, ((var, arg), &*env.arena.alloc(symbol)))); field_symbols_temp.push((alignment, symbol, ((var, arg), &*env.arena.alloc(symbol))));
@ -5511,6 +5592,7 @@ fn store_pattern_help<'a>(
} }
IntLiteral(_) IntLiteral(_)
| FloatLiteral(_) | FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
| StrLiteral(_) => { | StrLiteral(_) => {
@ -5645,6 +5727,7 @@ fn store_tag_pattern<'a>(
} }
IntLiteral(_) IntLiteral(_)
| FloatLiteral(_) | FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
| StrLiteral(_) => {} | StrLiteral(_) => {}
@ -5720,6 +5803,7 @@ fn store_newtype_pattern<'a>(
} }
IntLiteral(_) IntLiteral(_)
| FloatLiteral(_) | FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
| StrLiteral(_) => {} | StrLiteral(_) => {}
@ -5795,6 +5879,7 @@ fn store_record_destruct<'a>(
} }
IntLiteral(_) IntLiteral(_)
| FloatLiteral(_) | FloatLiteral(_)
| DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
| StrLiteral(_) => { | StrLiteral(_) => {
@ -6766,6 +6851,7 @@ pub enum Pattern<'a> {
Underscore, Underscore,
IntLiteral(i128), IntLiteral(i128),
FloatLiteral(u64), FloatLiteral(u64),
DecimalLiteral(RocDec),
BitLiteral { BitLiteral {
value: bool, value: bool,
tag_name: TagName, tag_name: TagName,
@ -6842,8 +6928,26 @@ fn from_can_pattern_help<'a>(
match can_pattern { match can_pattern {
Underscore => Ok(Pattern::Underscore), Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
IntLiteral(_, int) => Ok(Pattern::IntLiteral(*int as i128)), IntLiteral(_, _, int) => Ok(Pattern::IntLiteral(*int as i128)),
FloatLiteral(_, float) => Ok(Pattern::FloatLiteral(f64::to_bits(*float))), 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())), StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
Shadowed(region, ident) => Err(RuntimeError::Shadowing { Shadowed(region, ident) => Err(RuntimeError::Shadowing {
original_region: *region, original_region: *region,
@ -6854,12 +6958,18 @@ fn from_can_pattern_help<'a>(
// TODO preserve malformed problem information here? // TODO preserve malformed problem information here?
Err(RuntimeError::UnsupportedPattern(*region)) 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) { match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) {
IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)), 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))
}
} }
} }
@ -6872,7 +6982,8 @@ fn from_can_pattern_help<'a>(
use crate::exhaustive::Union; use crate::exhaustive::Union;
use crate::layout::UnionVariant::*; 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 { let variant = match res_variant {
Ok(cached) => cached, Ok(cached) => cached,
@ -7291,7 +7402,8 @@ fn from_can_pattern_help<'a>(
.. ..
} => { } => {
// sorted fields based on the type // 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 // sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
@ -7432,7 +7544,9 @@ fn from_can_record_destruct<'a>(
}) })
} }
#[derive(Debug)]
pub enum IntPrecision { pub enum IntPrecision {
Usize,
I128, I128,
I64, I64,
I32, I32,
@ -7468,6 +7582,7 @@ fn int_precision_to_builtin(precision: IntPrecision) -> Builtin<'static> {
I32 => Builtin::Int32, I32 => Builtin::Int32,
I16 => Builtin::Int16, I16 => Builtin::Int16,
I8 => Builtin::Int8, I8 => Builtin::Int8,
Usize => Builtin::Usize,
} }
} }
@ -7566,16 +7681,8 @@ pub fn num_argument_to_int_or_float(
Content::Alias(Symbol::NUM_NAT, _, _) Content::Alias(Symbol::NUM_NAT, _, _)
| Content::Alias(Symbol::NUM_NATURAL, _, _) | Content::Alias(Symbol::NUM_NATURAL, _, _)
| Content::Alias(Symbol::NUM_AT_NATURAL, _, _) => { | Content::Alias(Symbol::NUM_AT_NATURAL, _, _) => {
match ptr_bytes { IntOrFloat::UnsignedIntType(IntPrecision::Usize)
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
),
}
} }
other => { other => {
panic!( panic!(
@ -7865,7 +7972,15 @@ fn union_lambda_set_to_switch<'a>(
assigned: Symbol, assigned: Symbol,
hole: &'a Stmt<'a>, hole: &'a Stmt<'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()); 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 fn_args = fn_args.into_bump_slice();
let ret = arena.alloc(ret); 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)) Ok(Self::Function(fn_args, lambda_set, ret))
} }
@ -516,6 +517,7 @@ impl<'a> LambdaSet<'a> {
arena: &'a Bump, arena: &'a Bump,
subs: &Subs, subs: &Subs,
closure_var: Variable, closure_var: Variable,
ptr_bytes: u32,
) -> Result<Self, LayoutProblem> { ) -> Result<Self, LayoutProblem> {
let mut tags = std::vec::Vec::new(); let mut tags = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) { match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) {
@ -529,6 +531,7 @@ impl<'a> LambdaSet<'a> {
arena, arena,
subs, subs,
seen: Vec::new_in(arena), seen: Vec::new_in(arena),
ptr_bytes,
}; };
for (tag_name, variables) in tags.iter() { 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 { Ok(LambdaSet {
set: set.into_bump_slice(), set: set.into_bump_slice(),
@ -554,10 +558,10 @@ impl<'a> LambdaSet<'a> {
} }
Ok(()) | Err((_, Content::FlexVar(_))) => { Ok(()) | Err((_, Content::FlexVar(_))) => {
// TODO hack for builting functions. // this can happen when there is a type error somewhere
Ok(LambdaSet { Ok(LambdaSet {
set: &[], set: &[],
representation: arena.alloc(Layout::Struct(&[])), representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))),
}) })
} }
_ => panic!("called LambdaSet.from_var on invalid input"), _ => panic!("called LambdaSet.from_var on invalid input"),
@ -568,9 +572,10 @@ impl<'a> LambdaSet<'a> {
arena: &'a Bump, arena: &'a Bump,
subs: &Subs, subs: &Subs,
tags: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>, tags: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
ptr_bytes: u32,
) -> Layout<'a> { ) -> Layout<'a> {
// otherwise, this is a closure with a payload // 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::*; use UnionVariant::*;
match variant { match variant {
@ -648,6 +653,7 @@ pub enum Builtin<'a> {
} }
pub struct Env<'a, 'b> { pub struct Env<'a, 'b> {
ptr_bytes: u32,
arena: &'a Bump, arena: &'a Bump,
seen: Vec<'a, Variable>, seen: Vec<'a, Variable>,
subs: &'b Subs, 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`. /// 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 /// 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 /// But if we're careful when to invalidate certain keys, we still get some benefit
#[derive(Default, Debug)] #[derive(Debug)]
pub struct LayoutCache<'a> { pub struct LayoutCache<'a> {
ptr_bytes: u32,
_marker: std::marker::PhantomData<&'a u8>, _marker: std::marker::PhantomData<&'a u8>,
} }
@ -985,6 +992,13 @@ pub enum CachedLayout<'a> {
} }
impl<'a> LayoutCache<'a> { impl<'a> LayoutCache<'a> {
pub fn new(ptr_bytes: u32) -> Self {
Self {
ptr_bytes,
_marker: Default::default(),
}
}
pub fn from_var( pub fn from_var(
&mut self, &mut self,
arena: &'a Bump, arena: &'a Bump,
@ -998,6 +1012,7 @@ impl<'a> LayoutCache<'a> {
arena, arena,
subs, subs,
seen: Vec::new_in(arena), seen: Vec::new_in(arena),
ptr_bytes: self.ptr_bytes,
}; };
Layout::from_var(&mut env, var) Layout::from_var(&mut env, var)
@ -1016,6 +1031,7 @@ impl<'a> LayoutCache<'a> {
arena, arena,
subs, subs,
seen: Vec::new_in(arena), seen: Vec::new_in(arena),
ptr_bytes: self.ptr_bytes,
}; };
RawFunctionLayout::from_var(&mut env, var) 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 I16_SIZE: u32 = std::mem::size_of::<i16>() as u32;
const I8_SIZE: u32 = std::mem::size_of::<i8>() 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 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 DECIMAL_SIZE: u32 = std::mem::size_of::<i128>() as u32;
const F128_SIZE: u32 = 16; const F128_SIZE: u32 = 16;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
@ -1068,7 +1083,7 @@ impl<'a> Builtin<'a> {
Int16 => Builtin::I16_SIZE, Int16 => Builtin::I16_SIZE,
Int8 => Builtin::I8_SIZE, Int8 => Builtin::I8_SIZE,
Int1 => Builtin::I1_SIZE, Int1 => Builtin::I1_SIZE,
Usize => Builtin::USIZE_SIZE, Usize => pointer_size,
Decimal => Builtin::DECIMAL_SIZE, Decimal => Builtin::DECIMAL_SIZE,
Float128 => Builtin::F128_SIZE, Float128 => Builtin::F128_SIZE,
Float64 => Builtin::F64_SIZE, Float64 => Builtin::F64_SIZE,
@ -1095,7 +1110,7 @@ impl<'a> Builtin<'a> {
Int16 => align_of::<i16>() as u32, Int16 => align_of::<i16>() as u32,
Int8 => align_of::<i8>() as u32, Int8 => align_of::<i8>() as u32,
Int1 => align_of::<bool>() as u32, Int1 => align_of::<bool>() as u32,
Usize => align_of::<usize>() as u32, Usize => pointer_size,
Decimal => align_of::<i128>() as u32, Decimal => align_of::<i128>() as u32,
Float128 => align_of::<i128>() as u32, Float128 => align_of::<i128>() as u32,
Float64 => align_of::<f64>() as u32, Float64 => align_of::<f64>() as u32,
@ -1182,6 +1197,7 @@ fn layout_from_flat_type<'a>(
let arena = env.arena; let arena = env.arena;
let subs = env.subs; let subs = env.subs;
let ptr_bytes = env.ptr_bytes;
match flat_type { match flat_type {
Apply(symbol, args) => { Apply(symbol, args) => {
@ -1273,7 +1289,7 @@ fn layout_from_flat_type<'a>(
} }
} }
Func(_, closure_var, _) => { 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()) 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); let mut pairs = Vec::from_iter_in(pairs_it, arena);
pairs.sort_by(|(label1, layout1), (label2, layout2)| { pairs.sort_by(|(label1, layout1), (label2, layout2)| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes); let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.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) => { TagUnion(tags, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, 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) => { FunctionOrTagUnion(tag_name, _, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
let tags = UnionTags::from_tag_name_index(tag_name); 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) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, 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| { tag_layout.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes); let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes);
@ -1425,11 +1437,13 @@ pub fn sort_record_fields<'a>(
arena: &'a Bump, arena: &'a Bump,
var: Variable, var: Variable,
subs: &Subs, subs: &Subs,
ptr_bytes: u32,
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> { ) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
let mut env = Env { let mut env = Env {
arena, arena,
subs, subs,
seen: Vec::new_in(arena), seen: Vec::new_in(arena),
ptr_bytes,
}; };
let (it, _) = gather_fields_unsorted_iter(subs, RecordFields::empty(), var); let (it, _) = gather_fields_unsorted_iter(subs, RecordFields::empty(), var);
@ -1445,6 +1459,8 @@ fn sort_record_fields_help<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
fields_map: impl Iterator<Item = (Lowercase, RecordField<Variable>)>, fields_map: impl Iterator<Item = (Lowercase, RecordField<Variable>)>,
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> { ) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
let ptr_bytes = env.ptr_bytes;
// Sort the fields by label // Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.size_hint().0, env.arena); 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 { |(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 {
Ok(layout1) | Err(layout1) => match res_layout2 { Ok(layout1) | Err(layout1) => match res_layout2 {
Ok(layout2) | Err(layout2) => { Ok(layout2) | Err(layout2) => {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes); let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes);
@ -1605,6 +1619,7 @@ pub fn union_sorted_tags<'a>(
arena: &'a Bump, arena: &'a Bump,
var: Variable, var: Variable,
subs: &Subs, subs: &Subs,
ptr_bytes: u32,
) -> Result<UnionVariant<'a>, LayoutProblem> { ) -> Result<UnionVariant<'a>, LayoutProblem> {
let var = let var =
if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(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) { let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) {
Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => { Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => {
let opt_rec_var = get_recursion_var(subs, var); 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((_, Content::Error)) => return Err(LayoutProblem::Erroneous),
Err(other) => panic!("invalid content in tag union variable: {:?}", other), 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)>, mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>,
opt_rec_var: Option<Variable>, opt_rec_var: Option<Variable>,
subs: &Subs, subs: &Subs,
ptr_bytes: u32,
) -> UnionVariant<'a> { ) -> UnionVariant<'a> {
// sort up front; make sure the ordering stays intact! // sort up front; make sure the ordering stays intact!
tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
@ -1659,6 +1675,7 @@ fn union_sorted_tags_help_new<'a>(
arena, arena,
subs, subs,
seen: Vec::new_in(arena), seen: Vec::new_in(arena),
ptr_bytes,
}; };
match tags_vec.len() { match tags_vec.len() {
@ -1708,8 +1725,6 @@ fn union_sorted_tags_help_new<'a>(
} }
layouts.sort_by(|layout1, layout2| { layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes); let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.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| { arg_layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes); let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.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>)>, mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
opt_rec_var: Option<Variable>, opt_rec_var: Option<Variable>,
subs: &Subs, subs: &Subs,
ptr_bytes: u32,
) -> UnionVariant<'a> { ) -> UnionVariant<'a> {
// sort up front; make sure the ordering stays intact! // sort up front; make sure the ordering stays intact!
tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
@ -1875,6 +1889,7 @@ pub fn union_sorted_tags_help<'a>(
arena, arena,
subs, subs,
seen: Vec::new_in(arena), seen: Vec::new_in(arena),
ptr_bytes,
}; };
match tags_vec.len() { match tags_vec.len() {
@ -1921,8 +1936,6 @@ pub fn union_sorted_tags_help<'a>(
} }
layouts.sort_by(|layout1, layout2| { layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes); let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.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| { arg_layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes); let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes);
@ -2091,7 +2102,12 @@ fn cheap_sort_tags<'a, 'b>(
tags_vec 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)); debug_assert!(tags.is_newtype_wrapper(subs));
let slice_index = tags.variables().into_iter().next().unwrap(); 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, arena,
subs, subs,
seen: Vec::new_in(arena), seen: Vec::new_in(arena),
ptr_bytes,
}; };
match Layout::from_var(&mut env, var) { 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::*; use UnionVariant::*;
if tags.is_newtype_wrapper(subs) { 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); 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 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 { match variant {
Never => Layout::Union(UnionLayout::NonRecursive(&[])), Never => Layout::Union(UnionLayout::NonRecursive(&[])),

View file

@ -143,6 +143,8 @@ fn pattern_to_doc_help<'b>(
Bit(false) => alloc.text("False"), Bit(false) => alloc.text("False"),
Byte(b) => alloc.text(b.to_string()), Byte(b) => alloc.text(b.to_string()),
Float(f) => alloc.text(f.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()), Str(s) => alloc.string(s.into()),
}, },
Ctor(union, tag_id, args) => { 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(); 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 // 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 { let mut mono_env = roc_mono::ir::Env {
arena: &arena, arena: &arena,
subs: &mut subs, subs: &mut subs,
problems: &mut mono_problems, problems: &mut mono_problems,
home, home,
ident_ids: &mut ident_ids, ident_ids: &mut ident_ids,
ptr_bytes: 8, ptr_bytes,
update_mode_counter: 0, update_mode_counter: 0,
// call_specialization_counter=0 is reserved // call_specialization_counter=0 is reserved
call_specialization_counter: 1, call_specialization_counter: 1,

View file

@ -30,14 +30,19 @@ either = "1.6.1"
indoc = "0.3.3" indoc = "0.3.3"
libc = "0.2" libc = "0.2"
inkwell = { path = "../../vendor/inkwell" } inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10" target-lexicon = "0.12.2"
libloading = "0.6" libloading = "0.6"
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
tempfile = "3.1.0"
[dev-dependencies] [dev-dependencies]
maplit = "1.0.1" maplit = "1.0.1"
quickcheck = "0.8" quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
[features]
default = []
wasm-cli-run = []

View file

@ -1,7 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod gen_compare { mod gen_compare {
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,9 +1,8 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::RocStr; use roc_std::{RocList, RocStr};
#[test] #[test]
fn dict_empty_len() { fn dict_empty_len() {
@ -158,8 +157,8 @@ fn keys() {
Dict.keys myDict Dict.keys myDict
"# "#
), ),
&[0, 1, 2], RocList::from_slice(&[0, 1, 2]),
&[i64] RocList<i64>
); );
} }
@ -179,8 +178,8 @@ fn values() {
Dict.values myDict Dict.values myDict
"# "#
), ),
&[100, 200, 300], RocList::from_slice(&[100, 200, 300]),
&[i64] RocList<i64>
); );
} }
@ -197,8 +196,8 @@ fn from_list_with_fold() {
Dict.values myDict Dict.values myDict
"# "#
), ),
&[2, 3, 1], RocList::from_slice(&[2, 3, 1]),
&[i64] RocList<i64>
); );
assert_evals_to!( assert_evals_to!(
@ -242,8 +241,8 @@ fn small_str_keys() {
Dict.keys myDict Dict.keys myDict
"# "#
), ),
&[RocStr::from("c"), RocStr::from("a"), RocStr::from("b"),], RocList::from_slice(&[RocStr::from("c"), RocStr::from("a"), RocStr::from("b"),],),
&[RocStr] RocList<RocStr>
); );
} }
@ -263,12 +262,12 @@ fn big_str_keys() {
Dict.keys myDict Dict.keys myDict
"# "#
), ),
&[ RocList::from_slice(&[
RocStr::from("Leverage agile frameworks to provide a robust"), RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"), RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"), RocStr::from("synopsis for high level overviews. Iterative approaches"),
], ]),
&[RocStr] RocList<RocStr>
); );
} }
@ -287,12 +286,12 @@ fn big_str_values() {
Dict.values myDict Dict.values myDict
"# "#
), ),
&[ RocList::from_slice(&[
RocStr::from("Leverage agile frameworks to provide a robust"), RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"), RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"), RocStr::from("synopsis for high level overviews. Iterative approaches"),
], ]),
&[RocStr] RocList<RocStr>
); );
} }
@ -363,8 +362,8 @@ fn union_prefer_first() {
Dict.values myDict Dict.values myDict
"# "#
), ),
&[100], RocList::from_slice(&[100]),
&[i64] RocList<i64>
); );
} }
@ -423,8 +422,8 @@ fn intersection_prefer_first() {
|> Dict.values |> Dict.values
"# "#
), ),
&[4, 2], RocList::from_slice(&[4, 2]),
&[i64] RocList<i64>
); );
} }
@ -483,8 +482,8 @@ fn difference_prefer_first() {
|> Dict.values |> Dict.values
"# "#
), ),
&[5, 3, 1], RocList::from_slice(&[5, 3, 1]),
&[i64] RocList<i64>
); );
} }

View file

@ -1,7 +1,7 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,8 +1,8 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use crate::helpers::with_larger_debug_stack; use crate::helpers::with_larger_debug_stack;
//use crate::assert_wasm_evals_to as assert_evals_to;
use core::ffi::c_void; use core::ffi::c_void;
use indoc::indoc; use indoc::indoc;
use roc_std::{RocList, RocStr}; use roc_std::{RocList, RocStr};
@ -865,9 +865,12 @@ fn list_repeat() {
RocList<i64> RocList<i64>
); );
let empty_lists: &'static [&'static [i64]] = &[&[], &[]]; assert_evals_to!(
"List.repeat 2 []",
RocList::from_slice(&[RocList::default(), RocList::default()]),
RocList<RocList<i64>>
);
assert_evals_to!("List.repeat 2 []", empty_lists, &'static [&'static [i64]]);
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -878,8 +881,8 @@ fn list_repeat() {
List.repeat 2 noStrs List.repeat 2 noStrs
"# "#
), ),
empty_lists, RocList::from_slice(&[RocList::default(), RocList::default()]),
&'static [&'static [i64]] RocList<RocList<i64>>
); );
assert_evals_to!( assert_evals_to!(
@ -1419,10 +1422,8 @@ fn gen_wrap_first() {
wrapFirst [ 1, 2 ] wrapFirst [ 1, 2 ]
"# "#
), ),
// RocList::from_slice(&[1]), RocList::from_slice(&[1]),
// RocList<i64> RocList<i64>
&[1],
&'static [i64]
); );
} }
@ -1897,34 +1898,54 @@ fn list_product() {
#[test] #[test]
fn list_keep_oks() { fn list_keep_oks() {
assert_evals_to!("List.keepOks [] (\\x -> x)", 0, i64); assert_evals_to!("List.keepOks [] (\\x -> x)", 0, i64);
assert_evals_to!("List.keepOks [1,2] (\\x -> Ok x)", &[1, 2], &[i64]); assert_evals_to!(
assert_evals_to!("List.keepOks [1,2] (\\x -> x % 2)", &[1, 0], &[i64]); "List.keepOks [1,2] (\\x -> Ok x)",
assert_evals_to!("List.keepOks [Ok 1, Err 2] (\\x -> x)", &[1], &[i64]); RocList::from_slice(&[1, 2]),
RocList<i64>
);
assert_evals_to!(
"List.keepOks [1,2] (\\x -> x % 2)",
RocList::from_slice(&[1, 0]),
RocList<i64>
);
assert_evals_to!(
"List.keepOks [Ok 1, Err 2] (\\x -> x)",
RocList::from_slice(&[1]),
RocList<i64>
);
} }
#[test] #[test]
fn list_keep_errs() { fn list_keep_errs() {
assert_evals_to!("List.keepErrs [] (\\x -> x)", 0, i64); assert_evals_to!("List.keepErrs [] (\\x -> x)", 0, i64);
assert_evals_to!("List.keepErrs [1,2] (\\x -> Err x)", &[1, 2], &[i64]); assert_evals_to!(
"List.keepErrs [1,2] (\\x -> Err x)",
RocList::from_slice(&[1, 2]),
RocList<i64>
);
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
List.keepErrs [0,1,2] (\x -> x % 0 |> Result.mapErr (\_ -> 32)) List.keepErrs [0,1,2] (\x -> x % 0 |> Result.mapErr (\_ -> 32))
"# "#
), ),
&[32, 32, 32], RocList::from_slice(&[32, 32, 32]),
&[i64] RocList<i64>
); );
assert_evals_to!("List.keepErrs [Ok 1, Err 2] (\\x -> x)", &[2], &[i64]); assert_evals_to!(
"List.keepErrs [Ok 1, Err 2] (\\x -> x)",
RocList::from_slice(&[2]),
RocList<i64>
);
} }
#[test] #[test]
fn list_map_with_index() { fn list_map_with_index() {
assert_evals_to!( assert_evals_to!(
"List.mapWithIndex [0,0,0] (\\index, x -> index + x)", "List.mapWithIndex [0,0,0] (\\index, x -> Num.intCast index + x)",
&[0, 1, 2], RocList::from_slice(&[0, 1, 2]),
&[i64] RocList<i64>
); );
} }
@ -1935,11 +1956,15 @@ fn cleanup_because_exception() {
indoc!( indoc!(
r#" r#"
x = [ 1,2 ] x = [ 1,2 ]
5 + Num.maxInt + 3 + List.len x
five : I64
five = 5
five + Num.maxInt + 3 + (Num.intCast (List.len x))
"# "#
), ),
RocList::from_slice(&[false; 1]), 9,
RocList<bool> i64
); );
} }

View file

@ -1,7 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod gen_num { mod gen_num {
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::{RocDec, RocOrder}; use roc_std::{RocDec, RocOrder};
@ -752,7 +752,7 @@ mod gen_num {
y : Dec y : Dec
y = 2.4 y = 2.4
z : Dec z : Dec
z = 3 z = 3

View file

@ -891,6 +891,7 @@ fn overflow_frees_list() {
n : I64 n : I64
n = 9_223_372_036_854_775_807 + (Num.intCast (List.len myList)) n = 9_223_372_036_854_775_807 + (Num.intCast (List.len myList))
index : Nat
index = Num.intCast n index = Num.intCast n
List.get myList index List.get myList index
@ -1018,7 +1019,7 @@ fn specialize_closure() {
y = [1] y = [1]
f = \{} -> x f = \{} -> x
g = \{} -> x + List.len y g = \{} -> x + Num.intCast (List.len y)
[ f, g ] [ f, g ]
@ -2292,8 +2293,8 @@ fn build_then_apply_closure() {
(\_ -> x) {} (\_ -> x) {}
"# "#
), ),
"long string that is malloced", RocStr::from_slice(b"long string that is malloced"),
&'static str RocStr
); );
} }
@ -2398,7 +2399,7 @@ fn call_invalid_layout() {
} }
#[test] #[test]
#[should_panic(expected = "assert failed!")] #[should_panic(expected = "An expectation failed!")]
fn expect_fail() { fn expect_fail() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -2683,26 +2684,50 @@ fn list_walk_until() {
} }
#[test] #[test]
#[ignore] fn int_literal_not_specialized_with_annotation() {
fn int_literal_not_specialized() {
// see https://github.com/rtfeldman/roc/issues/1600 // see https://github.com/rtfeldman/roc/issues/1600
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app "test" provides [ main ] to "./platform" app "test" provides [ main ] to "./platform"
satisfy : (U8 -> Bool) -> Str
satisfy = \_ -> "foo"
main : I64
main = 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 _ -> 32
"# "#
), ),
@ -2736,3 +2761,21 @@ fn unresolved_tvar_when_capture_is_unused() {
i64 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

@ -1,7 +1,7 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,8 +1,8 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::RocList;
#[test] #[test]
fn empty_len() { fn empty_len() {
@ -38,8 +38,8 @@ fn single_to_list() {
Set.toList (Set.single 42) Set.toList (Set.single 42)
"# "#
), ),
&[42], RocList::from_slice(&[42]),
&[i64] RocList<i64>
); );
assert_evals_to!( assert_evals_to!(
@ -48,8 +48,8 @@ fn single_to_list() {
Set.toList (Set.single 1) Set.toList (Set.single 1)
"# "#
), ),
&[1], RocList::from_slice(&[1]),
&[i64] RocList<i64>
); );
assert_evals_to!( assert_evals_to!(
@ -58,8 +58,8 @@ fn single_to_list() {
Set.toList (Set.single 1.0) Set.toList (Set.single 1.0)
"# "#
), ),
&[1.0], RocList::from_slice(&[1.0]),
&[f64] RocList<f64>
); );
} }
@ -75,8 +75,8 @@ fn insert() {
|> Set.toList |> Set.toList
"# "#
), ),
&[0, 1, 2], RocList::from_slice(&[0, 1, 2]),
&[i64] RocList<i64>
); );
} }
@ -93,8 +93,8 @@ fn remove() {
|> Set.toList |> Set.toList
"# "#
), ),
&[0], RocList::from_slice(&[0]),
&[i64] RocList<i64>
); );
} }
@ -113,8 +113,8 @@ fn union() {
|> Set.toList |> Set.toList
"# "#
), ),
&[4, 2, 3, 1], RocList::from_slice(&[4, 2, 3, 1]),
&[i64] RocList<i64>
); );
} }
@ -133,8 +133,8 @@ fn difference() {
|> Set.toList |> Set.toList
"# "#
), ),
&[2], RocList::from_slice(&[2]),
&[i64] RocList<i64>
); );
} }
@ -153,8 +153,8 @@ fn intersection() {
|> Set.toList |> Set.toList
"# "#
), ),
&[1], RocList::from_slice(&[1]),
&[i64] RocList<i64>
); );
} }
@ -196,8 +196,6 @@ fn contains() {
#[test] #[test]
fn from_list() { fn from_list() {
let empty_list: &'static [i64] = &[];
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -206,8 +204,8 @@ fn from_list() {
|> Set.toList |> Set.toList
"# "#
), ),
&[4, 2, 3, 1], RocList::from_slice(&[4, 2, 3, 1]),
&[i64] RocList<i64>
); );
assert_evals_to!( assert_evals_to!(
@ -218,8 +216,8 @@ fn from_list() {
|> Set.toList |> Set.toList
"# "#
), ),
empty_list, RocList::default(),
&[i64] RocList<i64>
); );
assert_evals_to!( assert_evals_to!(
@ -233,7 +231,7 @@ fn from_list() {
|> Set.toList |> Set.toList
"# "#
), ),
empty_list, RocList::default(),
&[i64] RocList<i64>
); );
} }

View file

@ -3,33 +3,7 @@
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::RocStr; use roc_std::{RocList, RocStr};
use std::cmp::min;
const ROC_STR_MEM_SIZE: usize = core::mem::size_of::<RocStr>();
fn small_str(str: &str) -> [u8; ROC_STR_MEM_SIZE] {
let mut bytes: [u8; ROC_STR_MEM_SIZE] = Default::default();
let mut index: usize = 0;
while index < ROC_STR_MEM_SIZE {
bytes[index] = 0;
index += 1;
}
let str_bytes = str.as_bytes();
let output_len: usize = min(str_bytes.len(), ROC_STR_MEM_SIZE);
index = 0;
while index < output_len {
bytes[index] = str_bytes[index];
index += 1;
}
bytes[ROC_STR_MEM_SIZE - 1] = 0b1000_0000 ^ (output_len as u8);
bytes
}
#[test] #[test]
fn str_split_bigger_delimiter_small_str() { fn str_split_bigger_delimiter_small_str() {
@ -78,8 +52,8 @@ fn str_split_str_concat_repeated() {
"# "#
), ),
"JJJJJJJJJJJJJJJJJJJJJJJJJ", RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"),
&'static str RocStr
); );
} }
@ -96,8 +70,8 @@ fn str_split_small_str_bigger_delimiter() {
_ -> "" _ -> ""
"# "#
), ),
small_str("JJJ"), RocStr::from_slice(b"JJJ"),
[u8; ROC_STR_MEM_SIZE] RocStr
); );
} }
@ -109,8 +83,11 @@ fn str_split_big_str_small_delimiter() {
Str.split "01234567789abcdefghi?01234567789abcdefghi" "?" Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
"# "#
), ),
&["01234567789abcdefghi", "01234567789abcdefghi"], RocList::from_slice(&[
&'static [&'static str] RocStr::from_slice(b"01234567789abcdefghi"),
RocStr::from_slice(b"01234567789abcdefghi")
]),
RocList<RocStr>
); );
assert_evals_to!( assert_evals_to!(
@ -119,8 +96,11 @@ fn str_split_big_str_small_delimiter() {
Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch" Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
"# "#
), ),
&["01234567789abcdefghi ", " 01234567789abcdefghi"], RocList::from_slice(&[
&'static [&'static str] RocStr::from_slice(b"01234567789abcdefghi "),
RocStr::from_slice(b" 01234567789abcdefghi")
]),
RocList<RocStr>
); );
} }
@ -132,8 +112,12 @@ fn str_split_small_str_small_delimiter() {
Str.split "J!J!J" "!" Str.split "J!J!J" "!"
"# "#
), ),
&[small_str("J"), small_str("J"), small_str("J")], RocList::from_slice(&[
&'static [[u8; ROC_STR_MEM_SIZE]] RocStr::from_slice(b"J"),
RocStr::from_slice(b"J"),
RocStr::from_slice(b"J")
]),
RocList<RocStr>
); );
} }
@ -147,8 +131,8 @@ fn str_split_bigger_delimiter_big_strs() {
"than the delimiter which happens to be very very long" "than the delimiter which happens to be very very long"
"# "#
), ),
&["string to split is shorter"], RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]),
&'static [&'static str] RocList<RocStr>
); );
} }
@ -160,9 +144,9 @@ fn str_split_empty_strs() {
Str.split "" "" Str.split "" ""
"# "#
), ),
&[small_str("")], RocList::from_slice(&[RocStr::from_slice(b"")]),
&'static [[u8; ROC_STR_MEM_SIZE]] RocList<RocStr>
) );
} }
#[test] #[test]
@ -173,8 +157,8 @@ fn str_split_minimal_example() {
Str.split "a," "," Str.split "a," ","
"# "#
), ),
&[small_str("a"), small_str("")], RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]),
&'static [[u8; ROC_STR_MEM_SIZE]] RocList<RocStr>
) )
} }
@ -201,8 +185,12 @@ fn str_split_small_str_big_delimiter() {
"---- ---- ---- ---- ----" "---- ---- ---- ---- ----"
"# "#
), ),
&[small_str("1"), small_str("2"), small_str("")], RocList::from_slice(&[
&'static [[u8; ROC_STR_MEM_SIZE]] RocStr::from_slice(b"1"),
RocStr::from_slice(b"2"),
RocStr::from_slice(b"")
]),
RocList<RocStr>
); );
} }
@ -216,8 +204,12 @@ fn str_split_small_str_20_char_delimiter() {
"|-- -- -- -- -- -- |" "|-- -- -- -- -- -- |"
"# "#
), ),
&[small_str("3"), small_str("4"), small_str("")], RocList::from_slice(&[
&'static [[u8; ROC_STR_MEM_SIZE]] RocStr::from_slice(b"3"),
RocStr::from_slice(b"4"),
RocStr::from_slice(b"")
]),
RocList<RocStr>
); );
} }
@ -231,14 +223,14 @@ fn str_concat_big_to_big() {
"Second string that is also fairly long. Two long strings test things that might not appear with short strings." "Second string that is also fairly long. Two long strings test things that might not appear with short strings."
"# "#
), ),
"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings.", RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."),
&'static str RocStr
); );
} }
#[test] #[test]
fn small_str_literal() { fn small_str_literal() {
assert_evals_to!( assert_llvm_evals_to!(
"\"JJJJJJJJJJJJJJJ\"", "\"JJJJJJJJJJJJJJJ\"",
[ [
0x4a, 0x4a,
@ -267,7 +259,7 @@ fn small_str_zeroed_literal() {
// Verifies that we zero out unused bytes in the string. // Verifies that we zero out unused bytes in the string.
// This is important so that string equality tests don't randomly // This is important so that string equality tests don't randomly
// fail due to unused memory being there! // fail due to unused memory being there!
assert_evals_to!( assert_llvm_evals_to!(
"\"J\"", "\"J\"",
[ [
0x4a, 0x4a,
@ -293,7 +285,7 @@ fn small_str_zeroed_literal() {
#[test] #[test]
fn small_str_concat_empty_first_arg() { fn small_str_concat_empty_first_arg() {
assert_evals_to!( assert_llvm_evals_to!(
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#, r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
[ [
0x4a, 0x4a,
@ -319,7 +311,7 @@ fn small_str_concat_empty_first_arg() {
#[test] #[test]
fn small_str_concat_empty_second_arg() { fn small_str_concat_empty_second_arg() {
assert_evals_to!( assert_llvm_evals_to!(
r#"Str.concat "JJJJJJJJJJJJJJJ" """#, r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
[ [
0x4a, 0x4a,
@ -347,14 +339,14 @@ fn small_str_concat_empty_second_arg() {
fn small_str_concat_small_to_big() { fn small_str_concat_small_to_big() {
assert_evals_to!( assert_evals_to!(
r#"Str.concat "abc" " this is longer than 15 chars""#, r#"Str.concat "abc" " this is longer than 15 chars""#,
"abc this is longer than 15 chars", RocStr::from_slice(b"abc this is longer than 15 chars"),
&'static str RocStr
); );
} }
#[test] #[test]
fn small_str_concat_small_to_small_staying_small() { fn small_str_concat_small_to_small_staying_small() {
assert_evals_to!( assert_llvm_evals_to!(
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
[ [
0x4a, 0x4a,
@ -382,14 +374,14 @@ fn small_str_concat_small_to_small_staying_small() {
fn small_str_concat_small_to_small_overflow_to_big() { fn small_str_concat_small_to_small_overflow_to_big() {
assert_evals_to!( assert_evals_to!(
r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#, r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
"abcdefghijklmnopqrstuvwxyz", RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"),
&'static str RocStr
); );
} }
#[test] #[test]
fn str_concat_empty() { fn str_concat_empty() {
assert_evals_to!(r#"Str.concat "" """#, "", &'static str); assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
} }
#[test] #[test]
@ -511,10 +503,18 @@ fn str_from_int() {
); );
let max = format!("{}", i64::MAX); let max = format!("{}", i64::MAX);
assert_evals_to!(r#"Str.fromInt Num.maxInt"#, &max, &'static str); assert_evals_to!(
r#"Str.fromInt Num.maxInt"#,
RocStr::from_slice(max.as_bytes()),
RocStr
);
let min = format!("{}", i64::MIN); let min = format!("{}", i64::MIN);
assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str); assert_evals_to!(
r#"Str.fromInt Num.minInt"#,
RocStr::from_slice(min.as_bytes()),
RocStr
);
} }
#[test] #[test]
@ -785,8 +785,8 @@ fn nested_recursive_literal() {
printExpr expr printExpr expr
"# "#
), ),
"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))", RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"),
&'static str RocStr
); );
} }
@ -815,19 +815,23 @@ fn str_join_comma_single() {
#[test] #[test]
fn str_from_float() { 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] #[test]
fn str_to_utf8() { fn str_to_utf8() {
assert_evals_to!(r#"Str.toUtf8 "hello""#, &[104, 101, 108, 108, 111], &[u8]); assert_evals_to!(
r#"Str.toUtf8 "hello""#,
RocList::from_slice(&[104, 101, 108, 108, 111]),
RocList<u8>
);
assert_evals_to!( assert_evals_to!(
r#"Str.toUtf8 "this is a long string""#, r#"Str.toUtf8 "this is a long string""#,
&[ RocList::from_slice(&[
116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116, 114, 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116, 114,
105, 110, 103 105, 110, 103
], ]),
&[u8] RocList<u8>
); );
} }

View file

@ -1,7 +1,7 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::{RocList, RocStr}; use roc_std::{RocList, RocStr};
@ -517,8 +517,8 @@ fn if_guard_multiple() {
{ a: f 0, b: f 1, c: f 2, d: f 4 } { a: f 0, b: f 1, c: f 2, d: f 4 }
"# "#
), ),
(0, 1, 2, 0), [0, 1, 2, 0],
(i64, i64, i64, i64) [i64; 4]
); );
} }
@ -925,7 +925,7 @@ fn alignment_in_single_tag_pattern_match() {
} }
#[test] #[test]
fn alignment_in_multi_tag_construction() { fn alignment_in_multi_tag_construction_two() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r"# r"#
@ -939,7 +939,10 @@ fn alignment_in_multi_tag_construction() {
((32i64, true), 1), ((32i64, true), 1),
((i64, bool), u8) ((i64, bool), u8)
); );
}
#[test]
fn alignment_in_multi_tag_construction_three() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r"# r"#

View file

@ -1,3 +1,4 @@
use inkwell::module::Module;
use libloading::Library; use libloading::Library;
use roc_build::link::module_to_dylib; use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator; use roc_build::program::FunctionIterator;
@ -7,7 +8,9 @@ use roc_collections::all::{MutMap, MutSet};
use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_std::{RocDec, RocList, RocOrder, RocStr};
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use target_lexicon::Triple;
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
@ -27,16 +30,17 @@ pub fn test_builtin_defs(symbol: Symbol, var_store: &mut VarStore) -> Option<Def
// this is not actually dead code, but only used by cfg_test modules // this is not actually dead code, but only used by cfg_test modules
// so "normally" it is dead, only at testing time is it used // so "normally" it is dead, only at testing time is it used
#[allow(dead_code)] #[allow(clippy::too_many_arguments)]
#[inline(never)] fn create_llvm_module<'a>(
pub fn helper<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
src: &str, src: &str,
stdlib: &'a roc_builtins::std::StdLib, stdlib: &'a roc_builtins::std::StdLib,
is_gen_test: bool, is_gen_test: bool,
ignore_problems: bool, ignore_problems: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) { target: &Triple,
opt_level: OptLevel,
) -> (&'static str, String, &'a Module<'a>) {
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
let filename = PathBuf::from("Test.roc"); let filename = PathBuf::from("Test.roc");
@ -53,7 +57,6 @@ pub fn helper<'a>(
module_src = &temp; module_src = &temp;
} }
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let exposed_types = MutMap::default(); let exposed_types = MutMap::default();
@ -169,13 +172,7 @@ pub fn helper<'a>(
} }
let builder = context.create_builder(); 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
} else {
OptLevel::Optimize
};
let module = arena.alloc(module); let module = arena.alloc(module);
let (module_pass, function_pass) = let (module_pass, function_pass) =
@ -255,10 +252,318 @@ pub fn helper<'a>(
// Uncomment this to see the module's optimized LLVM instruction output: // Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();
let lib = module_to_dylib(env.module, &target, opt_level) (main_fn_name, delayed_errors.join("\n"), env.module)
.expect("Error loading compiled dylib for test"); }
(main_fn_name, delayed_errors.join("\n"), lib) #[allow(dead_code)]
#[inline(never)]
pub fn helper<'a>(
arena: &'a bumpalo::Bump,
src: &str,
stdlib: &'a roc_builtins::std::StdLib,
is_gen_test: bool,
ignore_problems: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) {
let target = target_lexicon::Triple::host();
let opt_level = if cfg!(debug_assertions) {
OptLevel::Normal
} else {
OptLevel::Optimize
};
let (main_fn_name, delayed_errors, module) = create_llvm_module(
arena,
src,
stdlib,
is_gen_test,
ignore_problems,
context,
&target,
opt_level,
);
let lib =
module_to_dylib(module, &target, opt_level).expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors, lib)
}
fn wasm32_target_tripple() -> Triple {
use target_lexicon::{Architecture, BinaryFormat};
let mut triple = Triple::unknown();
triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm;
triple
}
#[allow(dead_code)]
pub fn helper_wasm<'a>(
arena: &'a bumpalo::Bump,
src: &str,
stdlib: &'a roc_builtins::std::StdLib,
_is_gen_test: bool,
ignore_problems: bool,
context: &'a inkwell::context::Context,
) -> wasmer::Instance {
let target = wasm32_target_tripple();
let opt_level = if cfg!(debug_assertions) {
OptLevel::Normal
} else {
OptLevel::Optimize
};
let is_gen_test = false;
let (_main_fn_name, _delayed_errors, llvm_module) = create_llvm_module(
arena,
src,
stdlib,
is_gen_test,
ignore_problems,
context,
&target,
opt_level,
);
use inkwell::targets::{InitializationConfig, Target, TargetTriple};
let dir = tempfile::tempdir().unwrap();
let dir_path = dir.path();
// let zig_global_cache_path = std::path::PathBuf::from("/home/folkertdev/roc/wasm/mess");
let test_a_path = dir_path.join("test.a");
let test_wasm_path = dir_path.join("libmain.wasm");
Target::initialize_webassembly(&InitializationConfig::default());
let triple = TargetTriple::create("wasm32-unknown-unknown-wasm");
llvm_module.set_triple(&triple);
llvm_module.set_source_file_name("Test.roc");
let target_machine = Target::from_name("wasm32")
.unwrap()
.create_target_machine(
&triple,
"",
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
inkwell::OptimizationLevel::None,
inkwell::targets::RelocMode::Default,
inkwell::targets::CodeModel::Default,
)
.unwrap();
let file_type = inkwell::targets::FileType::Object;
target_machine
.write_to_file(llvm_module, file_type, &test_a_path)
.unwrap();
use std::process::Command;
// Command::new("/opt/wasi-sdk/bin/clang")
/*
Command::new("zig")
.current_dir(dir_path)
.args(&[
"cc",
"/home/folkertdev/roc/wasm/libmain.a",
test_a_path.to_str().unwrap(),
"-target",
"wasm32-wasi",
"-o",
test_wasm_path.to_str().unwrap(),
"--sysroot=/opt/wasi-sdk/share/wasi-sysroot/",
"-Xlinker", "--export-dynamic",
// "-Xlinker", "--allow-undefined"
// "--global-cache-dir",
// zig_global_cache_path.to_str().unwrap(),
])
.status()
.unwrap();
*/
Command::new("/home/folkertdev/Downloads/zig-linux-x86_64-0.9.0-dev.848+d5ef5da59/zig")
.current_dir(dir_path)
.args(&[
"wasm-ld",
"/home/folkertdev/roc/wasm/libmain.a",
"/home/folkertdev/roc/wasm/libc.a",
test_a_path.to_str().unwrap(),
"-o",
test_wasm_path.to_str().unwrap(),
"--export-dynamic",
"--allow-undefined",
"--no-entry",
])
.status()
.unwrap();
/*
Command::new("/home/folkertdev/Downloads/zig-linux-x86_64-0.9.0-dev.848+d5ef5da59/zig")
.current_dir(dir_path)
.args(&[
"build-lib",
"/home/folkertdev/roc/wasm/libmain.a",
test_a_path.to_str().unwrap(),
"-target",
"wasm32-wasi",
"-dynamic",
"-lc",
// "--global-cache-dir",
// zig_global_cache_path.to_str().unwrap(),
])
.status()
.unwrap();
*/
// now, do wasmer stuff
use wasmer::{Function, Instance, Module, Store};
let store = Store::default();
let module = Module::from_file(&store, &test_wasm_path).unwrap();
// First, we create the `WasiEnv`
use wasmer_wasi::WasiState;
let mut wasi_env = WasiState::new("hello")
// .args(&["world"])
// .env("KEY", "Value")
.finalize()
.unwrap();
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let mut import_object = wasi_env
.import_object(&module)
.unwrap_or_else(|_| wasmer::imports!());
{
let mut exts = wasmer::Exports::new();
let main_function = Function::new_native(&store, fake_wasm_main_function);
let ext = wasmer::Extern::Function(main_function);
exts.insert("main", ext);
let main_function = Function::new_native(&store, wasm_roc_panic);
let ext = wasmer::Extern::Function(main_function);
exts.insert("roc_panic", ext);
import_object.register("env", exts);
}
Instance::new(&module, &import_object).unwrap()
}
#[allow(dead_code)]
fn wasm_roc_panic(address: u32, tag_id: u32) {
match tag_id {
0 => {
let mut string = "";
MEMORY.with(|f| {
let memory = f.borrow().unwrap();
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(address);
let width = 100;
let c_ptr = (ptr.deref(memory, 0, width)).unwrap();
use libc::c_char;
use std::ffi::CStr;
let slice = unsafe { CStr::from_ptr(c_ptr as *const _ as *const c_char) };
string = slice.to_str().unwrap();
});
panic!("Roc failed with message: {:?}", string)
}
_ => todo!(),
}
}
use std::cell::RefCell;
thread_local! {
pub static MEMORY: RefCell<Option<&'static wasmer::Memory>> = RefCell::new(None);
}
#[allow(dead_code)]
fn fake_wasm_main_function(_: u32, _: u32) -> u32 {
panic!("wasm entered the main function; this should never happen!")
}
#[allow(dead_code)]
pub fn assert_wasm_evals_to_help<T>(src: &str, ignore_problems: bool) -> Result<T, String>
where
T: FromWasmMemory,
{
let arena = bumpalo::Bump::new();
let context = inkwell::context::Context::create();
// NOTE the stdlib must be in the arena; just taking a reference will segfault
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let is_gen_test = true;
let instance = crate::helpers::eval::helper_wasm(
&arena,
src,
stdlib,
is_gen_test,
ignore_problems,
&context,
);
let memory = instance.exports.get_memory("memory").unwrap();
crate::helpers::eval::MEMORY.with(|f| {
*f.borrow_mut() = Some(unsafe { std::mem::transmute(memory) });
});
let test_wrapper = instance.exports.get_function("test_wrapper").unwrap();
match test_wrapper.call(&[]) {
Err(e) => Err(format!("call to `test_wrapper`: {:?}", e)),
Ok(result) => {
let address = match result[0] {
wasmer::Value::I32(a) => a,
_ => panic!(),
};
let output = <T as crate::helpers::eval::FromWasmMemory>::decode(
memory,
// skip the RocCallResult tag id
address as u32 + 8,
);
Ok(output)
}
}
}
#[macro_export]
macro_rules! assert_wasm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => {
match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $ignore_problems) {
Err(msg) => panic!("Wasm test failed: {:?}", msg),
Ok(actual) => {
#[allow(clippy::bool_assert_comparison)]
assert_eq!($transform(actual), $expected, "Wasm test failed")
}
}
};
($src:expr, $expected:expr, $ty:ty) => {
$crate::assert_wasm_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity, false);
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
$crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false);
};
} }
#[macro_export] #[macro_export]
@ -288,41 +593,282 @@ macro_rules! assert_llvm_evals_to {
let expected = $expected; let expected = $expected;
#[allow(clippy::redundant_closure_call)] #[allow(clippy::redundant_closure_call)]
let given = $transform(success); let given = $transform(success);
assert_eq!(&given, &expected); assert_eq!(&given, &expected, "LLVM test failed");
}; };
run_jit_function!(lib, main_fn_name, $ty, transform, errors) run_jit_function!(lib, main_fn_name, $ty, transform, errors)
}; };
($src:expr, $expected:expr, $ty:ty) => {
$crate::assert_llvm_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity, false);
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
assert_llvm_evals_to!($src, $expected, $ty, $transform, false); $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
}; };
} }
#[macro_export] #[macro_export]
macro_rules! assert_evals_to { macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty) => {{ ($src:expr, $expected:expr, $ty:ty) => {{
assert_evals_to!($src, $expected, $ty, (|val| val)); assert_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity);
}}; }};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument. // Same as above, except with an additional transformation argument.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, false); #[cfg(feature = "wasm-cli-run")]
$crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false);
$crate::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
} }
}; };
} }
#[allow(dead_code)]
pub fn identity<T>(value: T) -> T {
value
}
#[macro_export] #[macro_export]
macro_rules! assert_non_opt_evals_to { macro_rules! assert_non_opt_evals_to {
($src:expr, $expected:expr, $ty:ty) => {{ ($src:expr, $expected:expr, $ty:ty) => {{
assert_llvm_evals_to!($src, $expected, $ty, (|val| val)); $crate::assert_llvm_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity);
}}; }};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument. // Same as above, except with an additional transformation argument.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, false); $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
} }
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {{ ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {{
assert_llvm_evals_to!($src, $expected, $ty, $transform); $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform);
}}; }};
} }
pub trait FromWasmMemory: Sized {
const SIZE_OF_WASM: usize;
const ALIGN_OF_WASM: usize;
const ACTUAL_WIDTH: usize = if (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM) == 0 {
Self::SIZE_OF_WASM
} else {
Self::SIZE_OF_WASM + (Self::ALIGN_OF_WASM - (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM))
};
fn decode(memory: &wasmer::Memory, offset: u32) -> Self;
}
macro_rules! from_wasm_memory_primitive_decode {
($type_name:ident) => {
const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>();
const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>();
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
use core::mem::MaybeUninit;
let mut output: MaybeUninit<Self> = MaybeUninit::uninit();
let width = std::mem::size_of::<Self>();
let ptr = output.as_mut_ptr();
let raw_ptr = ptr as *mut u8;
let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) };
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(offset as u32);
let foobar = (ptr.deref(memory, 0, width as u32)).unwrap();
let wasm_slice = unsafe { std::mem::transmute(foobar) };
slice.copy_from_slice(wasm_slice);
unsafe { output.assume_init() }
}
};
}
macro_rules! from_wasm_memory_primitive {
($($type_name:ident ,)+) => {
$(
impl FromWasmMemory for $type_name {
from_wasm_memory_primitive_decode!($type_name);
}
)*
}
}
from_wasm_memory_primitive!(
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder,
);
impl FromWasmMemory for () {
const SIZE_OF_WASM: usize = 0;
const ALIGN_OF_WASM: usize = 0;
fn decode(_: &wasmer::Memory, _: u32) -> Self {}
}
impl FromWasmMemory for RocStr {
const SIZE_OF_WASM: usize = 8;
const ALIGN_OF_WASM: usize = 4;
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let bytes = <u64 as FromWasmMemory>::decode(memory, offset);
let length = (bytes >> 32) as u32;
let elements = bytes as u32;
if length == 0 {
RocStr::default()
} else if (length as i32) < 0 {
// this is a small string
let last_byte = bytes.to_ne_bytes()[7];
let actual_length = (last_byte ^ 0b1000_0000) as usize;
let slice = &bytes.to_ne_bytes()[..actual_length as usize];
RocStr::from_slice(slice)
} else {
// this is a big string
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(elements);
let foobar = (ptr.deref(memory, 0, length)).unwrap();
let wasm_slice = unsafe { std::mem::transmute(foobar) };
RocStr::from_slice(wasm_slice)
}
}
}
impl<T: FromWasmMemory + Clone> FromWasmMemory for RocList<T> {
const SIZE_OF_WASM: usize = 8;
const ALIGN_OF_WASM: usize = 4;
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let bytes = <u64 as FromWasmMemory>::decode(memory, offset);
let length = (bytes >> 32) as u32;
let elements = bytes as u32;
let mut items = Vec::with_capacity(length as usize);
for i in 0..length {
let item = <T as FromWasmMemory>::decode(
memory,
elements + i * <T as FromWasmMemory>::SIZE_OF_WASM as u32,
);
items.push(item);
}
RocList::from_slice(&items)
}
}
impl<T: FromWasmMemory + Clone> FromWasmMemory for &'_ [T] {
const SIZE_OF_WASM: usize = 8;
const ALIGN_OF_WASM: usize = 4;
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let bytes = <u64 as FromWasmMemory>::decode(memory, offset);
let length = (bytes >> 32) as u32;
let elements = bytes as u32;
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(elements);
let width = <T as FromWasmMemory>::SIZE_OF_WASM as u32 * length;
let foobar = (ptr.deref(memory, 0, width)).unwrap();
let wasm_slice =
unsafe { std::slice::from_raw_parts(foobar as *const _ as *const _, length as usize) };
wasm_slice
}
}
impl<T: FromWasmMemory> FromWasmMemory for &'_ T {
const SIZE_OF_WASM: usize = 4;
const ALIGN_OF_WASM: usize = 4;
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let elements = <u32 as FromWasmMemory>::decode(memory, offset);
let actual = <T as FromWasmMemory>::decode(memory, elements);
let b = Box::new(actual);
std::boxed::Box::<T>::leak(b)
}
}
impl<T: FromWasmMemory + Clone, const N: usize> FromWasmMemory for [T; N] {
const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM;
const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM;
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(offset);
let width = <T as FromWasmMemory>::SIZE_OF_WASM as u32 * N as u32;
let foobar = (ptr.deref(memory, 0, width)).unwrap();
let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) };
wasm_slice.clone()
}
}
impl FromWasmMemory for usize {
const SIZE_OF_WASM: usize = 4;
const ALIGN_OF_WASM: usize = 4;
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
<u32 as FromWasmMemory>::decode(memory, offset) as usize
}
}
impl<T: FromWasmMemory, U: FromWasmMemory> FromWasmMemory for (T, U) {
const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM;
const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM);
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
assert!(
T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM,
"this function does not handle alignment"
);
let t = <T as FromWasmMemory>::decode(memory, offset);
let u = <U as FromWasmMemory>::decode(memory, offset + T::ACTUAL_WIDTH as u32);
(t, u)
}
}
const fn max2(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}
const fn max3(a: usize, b: usize, c: usize) -> usize {
max2(max2(a, b), c)
}
impl<T: FromWasmMemory, U: FromWasmMemory, V: FromWasmMemory> FromWasmMemory for (T, U, V) {
const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM;
const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM);
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
assert!(
T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM,
"this function does not handle alignment"
);
assert!(
U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM,
"this function does not handle alignment"
);
let t = <T as FromWasmMemory>::decode(memory, offset);
let u = <U as FromWasmMemory>::decode(memory, offset + T::ACTUAL_WIDTH as u32);
let v = <V as FromWasmMemory>::decode(
memory,
offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32,
);
(t, u, v)
}
}

View file

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

View file

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

View file

@ -78,6 +78,14 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* [Utopia](https://utopia.app/) integrated design and development environment for React. Design and code update each other, in real time. * [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) * [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 ### Voice Interaction Related
* We should label as many things as possible and expose jumps to those labels as shortkeys. * 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. The API and documentation are meant to interface with humans.
* [DocC](https://developer.apple.com/videos/play/wwdc2021/10166/) neat documentation approach for swift. * [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 ## General Plugin Ideas
### Ideas ### Ideas

View file

@ -1,5 +1,6 @@
pub const NOTHING_OPENED: &str = pub const NOTHING_OPENED: &str =
"Execute `cargo run edit` from the root folder of the repo to try the editor."; "Execute `cargo run edit` from the root folder of the repo to try the editor.";
pub const START_TIP: &str = r#"Currently supported: lists, records, string, numbers and value definitions. pub const START_TIP: &str = r#"Currently supported: lists, records, string, numbers and value definitions.
Use `Ctrl+Shift+Up` or `Cmd+Shift+Up` to select surrounding expression. Use `Ctrl+Shift+Up` or `Cmd+Shift+Up` to select surrounding expression.

1
examples/.gitignore vendored
View file

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

View file

@ -1,6 +1,8 @@
app app
*.o *.o
*.dSYM *.dSYM
*.ll
*.bc
libhost.a libhost.a
roc_app.ll roc_app.ll
roc_app.bc roc_app.bc

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 -> findPath = \costFn, moveFn, start, end ->
astar costFn moveFn end (initialModel start) astar costFn moveFn end (initialModel start)
@ -111,24 +111,24 @@ astar = \costFn, moveFn, goal, model ->
astar costFn moveFn goal modelWithCosts astar costFn moveFn goal modelWithCosts
takeStep = \moveFn, _goal, model, current -> # takeStep = \moveFn, _goal, model, current ->
modelPopped = # modelPopped =
{ model & # { model &
openSet: Set.remove model.openSet current, # openSet: Set.remove model.openSet current,
evaluated: Set.insert model.evaluated current, # evaluated: Set.insert model.evaluated current,
} # }
#
neighbors = moveFn current # neighbors = moveFn current
#
newNeighbors = Set.difference neighbors modelPopped.evaluated # newNeighbors = Set.difference neighbors modelPopped.evaluated
#
modelWithNeighbors = { modelPopped & openSet: Set.union modelPopped.openSet newNeighbors } # modelWithNeighbors = { modelPopped & openSet: Set.union modelPopped.openSet newNeighbors }
#
# a lot goes wrong here # # a lot goes wrong here
modelWithCosts = # modelWithCosts =
Set.walk newNeighbors (\n, m -> updateCost current n m) modelWithNeighbors # Set.walk newNeighbors (\n, m -> updateCost current n m) modelWithNeighbors
#
modelWithCosts # 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_size() i64;
extern fn roc__mainForHost_1_Fx_result_size() i64; extern fn roc__mainForHost_1_Fx_result_size() i64;
extern fn malloc(size: usize) callconv(.C) ?*c_void; const Align = extern struct { a: usize, b: usize };
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.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 { export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment;
return malloc(size); return malloc(size);
} }
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { 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 { 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 { export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr); const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; 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 {}; const Unit = extern struct {};
pub export fn main() u8 { pub fn main() u8 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
const size = @intCast(usize, roc__mainForHost_size()); 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); var output = @ptrCast([*]u8, raw_output);
defer { defer {
@ -71,17 +78,17 @@ pub export fn main() u8 {
roc__mainForHost_1_exposed(output); roc__mainForHost_1_exposed(output);
const elements = @ptrCast([*]u64, @alignCast(8, output)); const flag = @ptrCast(*u64, @alignCast(@alignOf(u64), output)).*;
var flag = elements[0];
if (flag == 0) { if (flag == 0) {
// all is well // 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); call_the_closure(closure_data_pointer);
} else { } 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; stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
return 0; return 0;
@ -92,6 +99,7 @@ pub export fn main() u8 {
const delta = to_seconds(ts2) - to_seconds(ts1); 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; stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;
return 0; return 0;
@ -103,7 +111,7 @@ fn to_seconds(tms: std.os.timespec) f64 {
fn call_the_closure(closure_data_pointer: [*]u8) void { fn call_the_closure(closure_data_pointer: [*]u8) void {
const size = roc__mainForHost_1_Fx_result_size(); 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); var output = @ptrCast([*]u8, raw_output);
defer { defer {
@ -135,7 +143,7 @@ pub export fn roc_fx_putInt(int: i64) i64 {
return 0; 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(); const stdout = std.io.getStdOut().writer();
for (rocPath.asSlice()) |char| { for (rocPath.asSlice()) |char| {
@ -143,8 +151,6 @@ pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
} }
stdout.print("\n", .{}) catch unreachable; stdout.print("\n", .{}) catch unreachable;
return 0;
} }
const GetInt = extern struct { 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 $ cargo run --release Hello.roc
``` ```
## Troubleshooting
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.
## Design Notes ## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure 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 $ cargo run --release Hello.roc
``` ```
## Troubleshooting
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.
## Design Notes ## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure 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; const Align = extern struct { a: usize, b: usize };
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.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 { export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment;
return malloc(size); return malloc(size);
} }
export fn roc_realloc(c_ptr: *c_void, old_size: usize, new_size: usize, alignment: u32) callconv(.C) ?*c_void { 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 { 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 { export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr); const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; 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; 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 {}; const Unit = extern struct {};
pub export fn main() i32 { pub fn main() u8 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();

View file

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

View file

@ -1,18 +1,14 @@
{ pkgs ? import <nixpkgs> {}}: { pkgs ? import <nixpkgs> { } }:
pkgs.stdenv.mkDerivation { pkgs.stdenv.mkDerivation {
name = "debugir"; name = "debugir";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "vaivaswatha"; owner = "vaivaswatha";
repo = "debugir"; repo = "debugir";
rev = "ed454ba264f30d2a70264357a31d94db3dd676eb"; rev = "db871e6cee7f653e284b226e2567a2574635247c";
sha256 = "08hrn66zn5pa8jk45msl9ipa8d1p7r9gmpknh41fyjr6c7qpmfrk"; sha256 = "0rgh9gawf92mjya1plxlgi9azkwca3gq8qa5hri18k4b7sbjm6lx";
}; };
buildInputs = with pkgs; [ buildInputs = with pkgs; [ cmake libxml2 llvmPackages_12.llvm.dev ];
cmake
libxml2
llvmPackages_12.llvm.dev
];
buildPhase = '' buildPhase = ''
mkdir build mkdir build
cd build cd build

View file

@ -44,6 +44,12 @@ pub struct RocList<T> {
length: usize, length: usize,
} }
impl<T: Clone> Clone for RocList<T> {
fn clone(&self) -> Self {
Self::from_slice(self.as_slice())
}
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Storage { pub enum Storage {
ReadOnly, ReadOnly,
@ -584,6 +590,10 @@ impl RocStr {
let raw_ptr = Self::get_element_ptr(raw_ptr as *mut u8); 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 // NOTE: using a memcpy here causes weird issues
let target_ptr = raw_ptr as *mut u8; let target_ptr = raw_ptr as *mut u8;
@ -610,7 +620,9 @@ impl RocStr {
} }
pub fn as_slice(&self) -> &[u8] { pub fn as_slice(&self) -> &[u8] {
if self.is_small_str() { if self.is_empty() {
&[]
} else if self.is_small_str() {
unsafe { core::slice::from_raw_parts(self.get_small_str_ptr(), self.len()) } unsafe { core::slice::from_raw_parts(self.get_small_str_ptr(), self.len()) }
} else { } else {
unsafe { core::slice::from_raw_parts(self.elements, self.length) } unsafe { core::slice::from_raw_parts(self.elements, self.length) }
@ -714,7 +726,7 @@ impl Clone for RocStr {
impl Drop for RocStr { impl Drop for RocStr {
fn drop(&mut self) { fn drop(&mut self) {
if !self.is_small_str() { if !self.is_small_str() && !self.is_empty() {
let storage_ptr = self.get_storage_ptr_mut(); let storage_ptr = self.get_storage_ptr_mut();
unsafe { unsafe {
@ -832,11 +844,9 @@ impl RocDec {
} }
}; };
let after_point = match parts.next() { let opt_after_point = match parts.next() {
Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => answer, Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => Some(answer),
_ => { _ => None,
return None;
}
}; };
// There should have only been one "." in the string! // There should have only been one "." in the string!
@ -845,22 +855,27 @@ impl RocDec {
} }
// Calculate the low digits - the ones after the decimal point. // Calculate the low digits - the ones after the decimal point.
let lo = match after_point.parse::<i128>() { let lo = match opt_after_point {
Ok(answer) => { Some(after_point) => {
// Translate e.g. the 1 from 0.1 into 10000000000000000000 match after_point.parse::<i128>() {
// by "restoring" the elided trailing zeroes to the number! Ok(answer) => {
let trailing_zeroes = Self::DECIMAL_PLACES as usize - after_point.len(); // Translate e.g. the 1 from 0.1 into 10000000000000000000
let lo = answer * 10i128.pow(trailing_zeroes as u32); // 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('-') { if !before_point.starts_with('-') {
lo lo
} else { } else {
-lo -lo
}
}
Err(_) => {
return None;
}
} }
} }
Err(_) => { None => 0,
return None;
}
}; };
// Calculate the high digits - the ones before the decimal point. // Calculate the high digits - the ones before the decimal point.

View file

@ -17,6 +17,7 @@ let
linuxInputs = with pkgs; linuxInputs = with pkgs;
lib.optionals stdenv.isLinux [ lib.optionals stdenv.isLinux [
glibc_multi
valgrind valgrind
vulkan-headers vulkan-headers
vulkan-loader vulkan-loader
@ -49,7 +50,6 @@ let
zig zig
# lib deps # lib deps
glibc_multi
libffi libffi
libxml2 libxml2
ncurses ncurses
@ -70,13 +70,9 @@ in pkgs.mkShell {
# Additional Env vars # Additional Env vars
LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}"; LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}";
NIXOS_GLIBC_PATH = "${pkgs.glibc_multi.out}/lib"; NIXOS_GLIBC_PATH =
if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else "";
LD_LIBRARY_PATH = with pkgs; LD_LIBRARY_PATH = with pkgs;
lib.makeLibraryPath ([ lib.makeLibraryPath
pkg-config ([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] ++ linuxInputs);
stdenv.cc.cc.lib
libffi
ncurses
zlib
] ++ linuxInputs);
} }