Merge branch 'trunk' into linker

This commit is contained in:
Brendan Hansknecht 2021-09-03 19:57:32 -07:00
commit 56dc278fae
57 changed files with 3279 additions and 1149 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:
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work tool; the Build Tools are just the CLI tools, which is all I wanted)
1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools"
1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools" [note: as of September 2021 this should no longer be necessary - the next time anyone tries this, please try it without this step and make a PR to delete this step if it's no longer needed!]
1. I launched the "x64 Native Tools Command Prompt for Visual Studio 2019" application (note: not the similarly-named "x86" one!)
1. Make sure [Python 2.7](https://www.python.org/) and [CMake 3.17](http://cmake.org/) are installed on your system.
1. I followed most of the steps under LLVM's [building from source instructions](https://github.com/llvm/llvm-project#getting-the-source-code-and-building-llvm) up to the `cmake -G ...` command, which didn't work for me. Instead, at that point I did the following step.

727
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
RUN apt -y install wget git
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libc++-dev libc++abi-dev g++ libunwind-dev pkg-config libx11-dev zlib1g-dev
RUN apt -y install libunwind-dev pkg-config libx11-dev zlib1g-dev
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
@ -77,6 +77,9 @@ check-typos:
test-rust:
FROM +copy-dirs
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 \
cargo test --release && sccache --show-stats

View File

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

View File

@ -71,6 +71,9 @@ inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
tempfile = "3.1.0"
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"

View File

@ -79,6 +79,7 @@ pub fn build_file<'a>(
Architecture::X86_64 => false,
Architecture::Aarch64(_) => false,
Architecture::Wasm32 => true,
Architecture::X86_32(_) => false,
_ => panic!(
"TODO gracefully handle unsupported architecture: {:?}",
target.architecture

View File

@ -16,7 +16,7 @@ use std::path::{Path, PathBuf};
use std::process;
use std::process::Command;
use target_lexicon::BinaryFormat;
use target_lexicon::{Architecture, Triple};
use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
pub mod build;
pub mod repl;
@ -58,8 +58,8 @@ pub fn build_app<'a>() -> App<'a> {
.long(FLAG_BACKEND)
.help("Choose a different backend")
// .requires(BACKEND)
.default_value("llvm")
.possible_values(&["llvm", "wasm", "asm"])
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
.required(false),
)
.arg(
@ -135,8 +135,8 @@ pub fn build_app<'a>() -> App<'a> {
.long(FLAG_BACKEND)
.help("Choose a different backend")
// .requires(BACKEND)
.default_value("llvm")
.possible_values(&["llvm", "wasm", "asm"])
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
.required(false),
)
.arg(
@ -180,25 +180,19 @@ pub enum BuildConfig {
BuildAndRun { roc_file_arg_index: usize },
}
fn wasm32_target_tripple() -> Triple {
let mut triple = Triple::unknown();
triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm;
triple
}
#[cfg(feature = "llvm")]
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use build::build_file;
use std::str::FromStr;
use BuildConfig::*;
let target = match matches.value_of(FLAG_BACKEND) {
Some("wasm") => wasm32_target_tripple(),
_ => Triple::host(),
let backend = match matches.value_of(FLAG_BACKEND) {
Some(name) => Backend::from_str(name).unwrap(),
None => Backend::default(),
};
let target = backend.to_triple();
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
@ -276,7 +270,23 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = match target.architecture {
Architecture::Wasm32 => Command::new("wasmtime"),
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),
};
@ -348,3 +358,116 @@ 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();
}
enum Backend {
Host,
X86_32,
X86_64,
Dev,
Wasm32,
Wasm32Dev,
}
impl Default for Backend {
fn default() -> Self {
Backend::Host
}
}
impl Backend {
const fn as_str(&self) -> &'static str {
match self {
Backend::Host => "host",
Backend::X86_32 => "x86_32",
Backend::X86_64 => "x86_64",
Backend::Dev => "dev",
Backend::Wasm32 => "wasm32",
Backend::Wasm32Dev => "wasm32_dev",
}
}
/// NOTE keep up to date!
const OPTIONS: &'static [&'static str] = &[
Backend::Host.as_str(),
Backend::X86_32.as_str(),
Backend::X86_64.as_str(),
Backend::Dev.as_str(),
Backend::Wasm32.as_str(),
Backend::Wasm32Dev.as_str(),
];
fn to_triple(&self) -> Triple {
let mut triple = Triple::unknown();
match self {
Backend::Host => Triple::host(),
Backend::X86_32 => {
triple.architecture = Architecture::X86_32(X86_32Architecture::I386);
triple.binary_format = BinaryFormat::Elf;
// TODO make this user-specified?
triple.operating_system = OperatingSystem::Linux;
triple
}
Backend::X86_64 => {
triple.architecture = Architecture::X86_64;
triple.binary_format = BinaryFormat::Elf;
triple
}
Backend::Dev => todo!(),
Backend::Wasm32 | Backend::Wasm32Dev => {
triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm;
triple
}
}
}
}
impl std::fmt::Display for Backend {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for Backend {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"host" => Ok(Backend::Host),
"x86_32" => Ok(Backend::X86_32),
"x86_64" => Ok(Backend::X86_64),
"dev" => Ok(Backend::Dev),
"wasm32" => Ok(Backend::Wasm32),
"wasm32_dev" => Ok(Backend::Wasm32Dev),
_ => Err(()),
}
}
}

View File

@ -132,7 +132,7 @@ pub fn gen_and_eval<'a>(
let builder = context.create_builder();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&context, "", ptr_bytes,
&target, &context, "",
));
// mark our zig-defined builtins as internal
@ -179,7 +179,7 @@ pub fn gen_and_eval<'a>(
interns,
module,
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
exposed_to_host: MutSet::default(),
};
@ -197,6 +197,9 @@ pub fn gen_and_eval<'a>(
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:
// env.module.print_to_stderr();

View File

@ -126,19 +126,15 @@ mod cli_run {
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
let out = run_cmd(
"wasmtime",
stdin,
&[file.with_file_name(executable_filename).to_str().unwrap()],
);
let path = file.with_file_name(executable_filename);
let stdout = crate::run_with_wasmer(&path, stdin);
if !&out.stdout.ends_with(expected_ending) {
if !stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, out
expected_ending, stdout
);
}
assert!(out.status.success());
}
/// This macro does two things.
@ -565,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

@ -66,6 +66,7 @@ pub fn build_zig_host_native(
emit_bin: &str,
zig_host_src: &str,
zig_str_path: &str,
target: &str,
) -> Output {
Command::new("zig")
.env_clear()
@ -87,6 +88,9 @@ pub fn build_zig_host_native(
"-fPIC",
"-O",
"ReleaseSafe",
// cross-compile?
"-target",
target,
])
.output()
.unwrap()
@ -99,6 +103,7 @@ pub fn build_zig_host_native(
emit_bin: &str,
zig_host_src: &str,
zig_str_path: &str,
_target: &str,
) -> Output {
use serde_json::Value;
@ -255,6 +260,18 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
&emit_bin,
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
"native",
)
}
Architecture::X86_32(_) => {
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(),
"i386-linux-gnu",
)
}
_ => panic!("Unsupported architecture {:?}", target.architecture),
@ -392,6 +409,32 @@ fn link_linux(
) -> io::Result<(Child, PathBuf)> {
let architecture = format!("{}-linux-gnu", target.architecture);
// Command::new("cp")
// .args(&[input_paths[0], "/home/folkertdev/roc/wasm/host.o"])
// .output()
// .unwrap();
//
// Command::new("cp")
// .args(&[input_paths[1], "/home/folkertdev/roc/wasm/app.o"])
// .output()
// .unwrap();
if let Architecture::X86_32(_) = target.architecture {
return Ok((
Command::new("zig")
.args(&["build-exe"])
.args(input_paths)
.args(&[
"-target",
"i386-linux-musl",
"-lc",
&format!("-femit-bin={}", output_path.to_str().unwrap()),
])
.spawn()?,
output_path,
));
}
let libcrt_path = library_path(["/usr", "lib", &architecture])
.or_else(|| library_path(["/usr", "lib"]))
.or_else(|| library_path([&nixos_path()]))

View File

@ -42,10 +42,18 @@ pub fn gen_from_mono_module(
use std::time::SystemTime;
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*,
DEFAULT_PALETTE,
};
let code_gen_start = SystemTime::now();
let palette = DEFAULT_PALETTE;
// This will often over-allocate total memory, but it means we definitely
// never need to re-allocate either the warnings or the errors vec!
let total_problems = loaded.total_problems();
let mut warnings = Vec::with_capacity(total_problems);
let mut errors = Vec::with_capacity(total_problems);
for (home, (module_path, src)) in loaded.sources {
let mut src_lines: Vec<&str> = Vec::new();
@ -56,7 +64,6 @@ pub fn gen_from_mono_module(
} else {
src_lines.extend(src.split('\n'));
}
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns);
@ -64,38 +71,83 @@ pub fn gen_from_mono_module(
let problems = loaded.can_problems.remove(&home).unwrap_or_default();
for problem in problems.into_iter() {
let report = can_problem(&alloc, module_path.clone(), problem);
let severity = report.severity;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
println!("\n{}\n", buf);
match severity {
Warning => {
warnings.push(buf);
}
RuntimeError => {
errors.push(buf);
}
}
}
let problems = loaded.type_problems.remove(&home).unwrap_or_default();
for problem in problems {
let report = type_problem(&alloc, module_path.clone(), problem);
let severity = report.severity;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
println!("\n{}\n", buf);
match severity {
Warning => {
warnings.push(buf);
}
RuntimeError => {
errors.push(buf);
}
}
}
let problems = loaded.mono_problems.remove(&home).unwrap_or_default();
for problem in problems {
let report = mono_problem(&alloc, module_path.clone(), problem);
let severity = report.severity;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
println!("\n{}\n", buf);
match severity {
Warning => {
warnings.push(buf);
}
RuntimeError => {
errors.push(buf);
}
}
}
}
// Only print warnings if there are no errors
if errors.is_empty() {
for warning in warnings {
println!("\n{}\n", warning);
}
} else {
for error in errors {
println!("\n{}\n", error);
}
}
// If we printed any problems, print a horizontal rule at the end,
// and then clear any ANSI escape codes (e.g. colors) we've used.
//
// The horizontal rule is nice when running the program right after
// compiling it, as it lets you clearly see where the compiler
// errors/warnings end and the program output begins.
if total_problems > 0 {
println!("{}\u{001B}[0m\n", Report::horizontal_rule(&palette));
}
// Generate the binary
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let context = Context::create();
let module = arena.alloc(module_from_builtins(&context, "app", ptr_bytes));
let module = arena.alloc(module_from_builtins(target, &context, "app"));
// strip Zig debug stuff
// module.strip_debug_info();
@ -125,6 +177,7 @@ pub fn gen_from_mono_module(
|| name.starts_with("roc_builtins.dec")
|| name.starts_with("list.RocList")
|| name.starts_with("dict.RocDict")
|| name.contains("decref")
{
function.add_attribute(AttributeLoc::Function, enum_attr);
}
@ -221,7 +274,7 @@ pub fn gen_from_mono_module(
use target_lexicon::Architecture;
match target.architecture {
Architecture::X86_64 | Architecture::Aarch64(_) => {
Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => {
// assemble the .ll into a .bc
let _ = Command::new("llvm-as")
.args(&[
@ -272,7 +325,7 @@ pub fn gen_from_mono_module(
// Emit the .o file
use target_lexicon::Architecture;
match target.architecture {
Architecture::X86_64 | Architecture::Aarch64(_) => {
Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => {
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine =

View File

@ -17,6 +17,11 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
operating_system: OperatingSystem::Linux,
..
} => "x86_64-unknown-linux-gnu",
Triple {
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
operating_system: OperatingSystem::Linux,
..
} => "i386-unknown-linux-gnu",
Triple {
architecture: Architecture::Wasm32,
..
@ -38,7 +43,7 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
#[cfg(feature = "llvm")]
pub fn init_arch(target: &Triple) {
match target.architecture {
Architecture::X86_64 => {
Architecture::X86_64 | Architecture::X86_32(_) => {
Target::initialize_x86(&InitializationConfig::default());
}
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => {
@ -66,6 +71,7 @@ pub fn arch_str(target: &Triple) -> &'static str {
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
match target.architecture {
Architecture::X86_64 => "x86-64",
Architecture::X86_32(_) => "x86",
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => "aarch64",
Architecture::Arm(_) if cfg!(feature = "target-arm") => "arm",
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => "wasm32",

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

@ -1,6 +1,7 @@
const std = @import("std");
const mem = std.mem;
const Builder = std.build.Builder;
const CrossTarget = std.zig.CrossTarget;
pub fn build(b: *Builder) void {
// b.setPreferredReleaseMode(builtin.Mode.Debug
@ -20,7 +21,7 @@ pub fn build(b: *Builder) void {
test_step.dependOn(&main_tests.step);
// LLVM IR
const obj_name = "builtins-64bit";
const obj_name = "builtins-host";
const llvm_obj = b.addObject(obj_name, main_path);
llvm_obj.setBuildMode(mode);
llvm_obj.linkSystemLibrary("c");
@ -30,30 +31,48 @@ pub fn build(b: *Builder) void {
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step);
// 32-bit x86, useful for debugging
var i386_target = CrossTarget.parse(.{}) catch unreachable;
i386_target.cpu_arch = std.Target.Cpu.Arch.i386;
i386_target.os_tag = std.Target.Os.Tag.linux;
i386_target.abi = std.Target.Abi.musl;
const obj_name_i386 = "builtins-i386";
const llvm_obj_i386 = b.addObject(obj_name_i386, main_path);
llvm_obj_i386.setBuildMode(mode);
llvm_obj_i386.strip = true;
llvm_obj_i386.emit_llvm_ir = true;
llvm_obj_i386.emit_bin = false;
llvm_obj_i386.target = i386_target;
const ir_i386 = b.step("ir-i386", "Build LLVM ir for 32-bit targets (x86)");
ir_i386.dependOn(&llvm_obj_i386.step);
// LLVM IR 32-bit (wasm)
var target = b.standardTargetOptions(.{});
target.os_tag = std.Target.Os.Tag.linux;
target.cpu_arch = std.Target.Cpu.Arch.i386;
// target.abi = std.Target.Abi.none;
target.abi = std.Target.Abi.musl;
var wasm32_target = CrossTarget.parse(.{}) catch unreachable;
const obj_name_32bit = "builtins-32bit";
const llvm_obj_32bit = b.addObject(obj_name_32bit, main_path);
llvm_obj_32bit.setBuildMode(mode);
llvm_obj_32bit.linkSystemLibrary("c");
llvm_obj_32bit.strip = true;
llvm_obj_32bit.emit_llvm_ir = true;
llvm_obj_32bit.emit_bin = false;
llvm_obj_32bit.target = target;
// 32-bit wasm
wasm32_target.cpu_arch = std.Target.Cpu.Arch.wasm32;
wasm32_target.os_tag = std.Target.Os.Tag.wasi;
wasm32_target.abi = std.Target.Abi.none;
const ir32bit = b.step("ir-32bit", "Build LLVM ir for 32-bit targets (wasm)");
ir32bit.dependOn(&llvm_obj_32bit.step);
const obj_name_wasm32 = "builtins-wasm32";
const llvm_obj_wasm32 = b.addObject(obj_name_wasm32, main_path);
llvm_obj_wasm32.setBuildMode(mode);
llvm_obj_wasm32.strip = true;
llvm_obj_wasm32.emit_llvm_ir = true;
llvm_obj_wasm32.emit_bin = false;
llvm_obj_wasm32.target = wasm32_target;
const ir_wasm32 = b.step("ir-wasm32", "Build LLVM ir for 32-bit targets (wasm)");
ir_wasm32.dependOn(&llvm_obj_wasm32.step);
// Object File
// TODO: figure out how to get this to emit symbols that are only scoped to linkage (global but hidden).
// Also, zig has -ffunction-sections, but I am not sure how to add it here.
// With both of those changes, unused zig functions will be cleaned up by the linker saving around 100k.
const obj = b.addObject(obj_name, main_path);
const obj = b.addObject("builtins-host", main_path);
obj.setBuildMode(mode);
obj.linkSystemLibrary("c");
obj.setOutputDir(".");

View File

@ -66,11 +66,14 @@ fn capacityOfLevel(input: usize) usize {
const Alignment = extern struct {
bits: u8,
const VALUE_BEFORE_KEY_FLAG = 0b1000_0000;
const VALUE_BEFORE_KEY_FLAG: u8 = 0b1000_0000;
fn toU32(self: Alignment) u32 {
// xor to wipe the leftmost bit
return self.bits ^ Alignment.VALUE_BEFORE_KEY_FLAG;
if (self.bits >= VALUE_BEFORE_KEY_FLAG) {
return self.bits ^ Alignment.VALUE_BEFORE_KEY_FLAG;
} else {
return self.bits;
}
}
fn keyFirst(self: Alignment) bool {

View File

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

View File

@ -79,7 +79,7 @@ pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
// const stderr = std.io.getStdErr().writer();
// 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;
@ -104,6 +104,19 @@ pub const IntWidth = enum(u8) {
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(
bytes_or_null: ?[*]u8,
data_bytes: usize,
@ -117,84 +130,73 @@ pub fn decref(
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@sizeOf(isize), bytes));
const refcount = (isizes - 1)[0];
const refcount_isize = @bitCast(isize, refcount);
decref_ptr_to_refcount(isizes - 1, alignment);
}
switch (alignment) {
16 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(bytes - 16, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
8 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(bytes - 8, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
4 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(bytes - 4, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
else => {
// NOTE enums can currently have an alignment of < 8
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(bytes - 8, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
inline fn decref_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
) void {
const refcount: isize = refcount_ptr[0];
const extra_bytes = std.math.max(alignment, @sizeOf(usize));
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
} else if (refcount < 0) {
refcount_ptr[0] = refcount - 1;
}
}
pub fn allocateWithRefcount(
data_bytes: usize,
alignment: u32,
element_alignment: u32,
) [*]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) {
16 => {
const length = 2 * @sizeOf(usize) + data_bytes;
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment));
var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place) {
as_usize_array[0] = 0;
as_usize_array[1] = @intCast(usize, number_of_slots);
} else {
as_usize_array[0] = 0;
as_usize_array[1] = REFCOUNT_ONE;
}
as_usize_array[0] = 0;
as_usize_array[1] = REFCOUNT_ONE;
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;
},
else => {
const length = @sizeOf(usize) + data_bytes;
var new_bytes: [*]align(8) u8 = @alignCast(8, alloc(length, alignment));
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;
// const stdout = std.io.getStdOut().writer();
// stdout.print("alignment: {d}", .{alignment}) catch unreachable;
// @panic("allocateWithRefcount with invalid alignment");
unreachable;
},
}
}

View File

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

View File

@ -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 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.hashTestOnly : Nat, v -> Nat
// Dict.hashTestOnly : U64, v -> U64
add_top_level_function_type!(
Symbol::DICT_TEST_HASH,
vec![u64_type(), flex(TVAR2)],
Box::new(nat_type())
Box::new(u64_type())
);
// len : Dict * * -> Nat

View File

@ -1166,8 +1166,8 @@ fn num_max_i128(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 bool_var = var_store.fresh();
let len_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
let len_var = Variable::NAT;
let unbound_zero_var = Variable::NATURAL;
let body = RunLowLevel {
op: LowLevel::Eq,
@ -2198,7 +2198,22 @@ fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Dict.len : Dict * * -> Nat
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 * *
@ -2873,9 +2888,9 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_var = var_store.fresh();
let bool_var = var_store.fresh();
let list_var = var_store.fresh();
let len_var = var_store.fresh();
let num_var = var_store.fresh();
let num_precision_var = var_store.fresh();
let len_var = Variable::NAT;
let num_var = len_var;
let num_precision_var = Variable::NATURAL;
let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh();

View File

@ -200,6 +200,7 @@ pub struct Backend64Bit<
relocs: Vec<'a, Relocation>,
last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
layout_map: MutMap<Symbol, *const Layout<'a>>,
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
literal_map: MutMap<Symbol, Literal<'a>>,
@ -237,9 +238,10 @@ impl<
phantom_asm: PhantomData,
phantom_cc: PhantomData,
env,
buf: bumpalo::vec!(in env.arena),
relocs: bumpalo::vec!(in env.arena),
buf: bumpalo::vec![in env.arena],
relocs: bumpalo::vec![in env.arena],
last_seen_map: MutMap::default(),
layout_map: MutMap::default(),
free_map: MutMap::default(),
symbol_storage_map: MutMap::default(),
literal_map: MutMap::default(),
@ -262,6 +264,7 @@ impl<
self.stack_size = 0;
self.fn_call_stack_size = 0;
self.last_seen_map.clear();
self.layout_map.clear();
self.free_map.clear();
self.symbol_storage_map.clear();
self.buf.clear();
@ -285,6 +288,10 @@ impl<
&mut self.last_seen_map
}
fn layout_map(&mut self) -> &mut MutMap<Symbol, *const Layout<'a>> {
&mut self.layout_map
}
fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>) {
self.free_map = map;
}
@ -428,7 +435,7 @@ impl<
Ok(())
}
x => Err(format!(
"receiving return type, {:?}, is not yet implemented",
"FnCall: receiving return type, {:?}, is not yet implemented",
x
)),
}
@ -475,7 +482,7 @@ impl<
}
} else {
return Err(format!(
"branch info, {:?}, is not yet implemented in switch statemens",
"Switch: branch info, {:?}, is not yet implemented",
branch_info
));
}
@ -497,86 +504,116 @@ impl<
Ok(())
} else {
Err(format!(
"branch info, {:?}, is not yet implemented in switch statemens",
"Switch: branch info, {:?}, is not yet implemented",
branch_info
))
}
}
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> {
let dst_reg = self.claim_general_reg(dst)?;
let src_reg = self.load_to_general_reg(src)?;
ASM::abs_reg64_reg64(&mut self.buf, dst_reg, src_reg);
Ok(())
fn build_num_abs(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
match layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src_reg = self.load_to_general_reg(src)?;
ASM::abs_reg64_reg64(&mut self.buf, dst_reg, src_reg);
Ok(())
}
Layout::Builtin(Builtin::Float64) => {
let dst_reg = self.claim_float_reg(dst)?;
let src_reg = self.load_to_float_reg(src)?;
ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
Ok(())
}
x => Err(format!("NumAbs: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_abs_f64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> {
let dst_reg = self.claim_float_reg(dst)?;
let src_reg = self.load_to_float_reg(src)?;
ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
Ok(())
}
fn build_num_add_i64(
fn build_num_add(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
match layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
Layout::Builtin(Builtin::Float64) => {
let dst_reg = self.claim_float_reg(dst)?;
let src1_reg = self.load_to_float_reg(src1)?;
let src2_reg = self.load_to_float_reg(src2)?;
ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
x => Err(format!("NumAdd: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_add_f64(
fn build_num_mul(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
let dst_reg = self.claim_float_reg(dst)?;
let src1_reg = self.load_to_float_reg(src1)?;
let src2_reg = self.load_to_float_reg(src2)?;
ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
match layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
x => Err(format!("NumMul: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_mul_i64(
fn build_num_sub(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
match layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
x => Err(format!("NumSub: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_sub_i64(
fn build_eq(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String> {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
fn build_eq_i64(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol) -> Result<(), String> {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
match arg_layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
x => Err(format!("NumEq: layout, {:?}, not implemented yet", x)),
}
}
fn create_struct(
@ -849,7 +886,7 @@ impl<
Ok(reg)
}
Some(SymbolStorage::GeneralReg(_)) | Some(SymbolStorage::BaseAndGeneralReg(_, _)) => {
Err("Cannot load integer point symbol into FloatReg".to_string())
Err("Cannot load integer symbol into FloatReg".to_string())
}
None => Err(format!("Unknown symbol: {}", sym)),
}

View File

@ -79,6 +79,9 @@ where
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset();
self.load_args(proc.args, &proc.ret_layout)?;
for (layout, sym) in proc.args {
self.set_layout_map(*sym, layout)?;
}
// let start = std::time::Instant::now();
self.scan_ast(&proc.body);
self.create_free_map();
@ -94,6 +97,7 @@ where
match stmt {
Stmt::Let(sym, expr, layout, following) => {
self.build_expr(sym, expr, layout)?;
self.set_layout_map(*sym, layout)?;
self.free_symbols(stmt);
self.build_stmt(following, ret_layout)?;
Ok(())
@ -165,42 +169,76 @@ where
} => {
// For most builtins instead of calling a function, we can just inline the low level.
match *func_sym {
Symbol::NUM_ABS => {
self.build_run_low_level(sym, &LowLevel::NumAbs, arguments, layout)
}
Symbol::NUM_ADD => {
self.build_run_low_level(sym, &LowLevel::NumAdd, arguments, layout)
}
Symbol::NUM_ACOS => {
self.build_run_low_level(sym, &LowLevel::NumAcos, arguments, layout)
}
Symbol::NUM_ASIN => {
self.build_run_low_level(sym, &LowLevel::NumAsin, arguments, layout)
}
Symbol::NUM_ATAN => {
self.build_run_low_level(sym, &LowLevel::NumAtan, arguments, layout)
}
Symbol::NUM_MUL => {
self.build_run_low_level(sym, &LowLevel::NumMul, arguments, layout)
}
Symbol::NUM_ABS => self.build_run_low_level(
sym,
&LowLevel::NumAbs,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ADD => self.build_run_low_level(
sym,
&LowLevel::NumAdd,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ACOS => self.build_run_low_level(
sym,
&LowLevel::NumAcos,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ASIN => self.build_run_low_level(
sym,
&LowLevel::NumAsin,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ATAN => self.build_run_low_level(
sym,
&LowLevel::NumAtan,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_MUL => self.build_run_low_level(
sym,
&LowLevel::NumMul,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_POW_INT => self.build_run_low_level(
sym,
&LowLevel::NumPowInt,
arguments,
layout,
arg_layouts,
ret_layout,
),
Symbol::NUM_SUB => self.build_run_low_level(
sym,
&LowLevel::NumSub,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_SUB => {
self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout)
}
Symbol::NUM_ROUND => self.build_run_low_level(
sym,
&LowLevel::NumRound,
arguments,
layout,
arg_layouts,
ret_layout,
),
Symbol::BOOL_EQ => self.build_run_low_level(
sym,
&LowLevel::Eq,
arguments,
arg_layouts,
ret_layout,
),
Symbol::BOOL_EQ => {
self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout)
}
x if x
.module_string(&self.env().interns)
.starts_with(ModuleName::APP) =>
@ -217,7 +255,25 @@ where
}
CallType::LowLevel { op: lowlevel, .. } => {
self.build_run_low_level(sym, lowlevel, arguments, layout)
let mut arg_layouts: bumpalo::collections::Vec<Layout<'a>> =
bumpalo::vec![in self.env().arena];
arg_layouts.reserve(arguments.len());
let layout_map = self.layout_map();
for arg in *arguments {
if let Some(layout) = layout_map.get(arg) {
// This is safe because every value in the map is always set with a valid layout and cannot be null.
arg_layouts.push(unsafe { *(*layout) });
} else {
return Err(format!("the argument, {:?}, has no know layout", arg));
}
}
self.build_run_low_level(
sym,
lowlevel,
arguments,
arg_layouts.into_bump_slice(),
layout,
)
}
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
}
@ -242,76 +298,119 @@ where
sym: &Symbol,
lowlevel: &LowLevel,
args: &'a [Symbol],
layout: &Layout<'a>,
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args)?;
match lowlevel {
LowLevel::NumAbs => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {
Layout::Builtin(Builtin::Int64) => self.build_num_abs_i64(sym, &args[0]),
Layout::Builtin(Builtin::Float64) => self.build_num_abs_f64(sym, &args[0]),
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
debug_assert_eq!(
1,
args.len(),
"NumAbs: expected to have exactly one argument"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumAbs: expected to have the same argument and return layout"
);
self.build_num_abs(sym, &args[0], ret_layout)
}
LowLevel::NumAdd => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {
Layout::Builtin(Builtin::Int64) => {
self.build_num_add_i64(sym, &args[0], &args[1])
}
Layout::Builtin(Builtin::Float64) => {
self.build_num_add_f64(sym, &args[0], &args[1])
}
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
}
LowLevel::NumAcos => {
self.build_fn_call(sym, bitcode::NUM_ACOS.to_string(), args, &[*layout], layout)
}
LowLevel::NumAsin => {
self.build_fn_call(sym, bitcode::NUM_ASIN.to_string(), args, &[*layout], layout)
}
LowLevel::NumAtan => {
self.build_fn_call(sym, bitcode::NUM_ATAN.to_string(), args, &[*layout], layout)
debug_assert_eq!(
2,
args.len(),
"NumAdd: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NumAdd: expected all arguments of to have the same layout"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumAdd: expected to have the same argument and return layout"
);
self.build_num_add(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumAcos => self.build_fn_call(
sym,
bitcode::NUM_ACOS.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::NumAsin => self.build_fn_call(
sym,
bitcode::NUM_ASIN.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::NumAtan => self.build_fn_call(
sym,
bitcode::NUM_ATAN.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::NumMul => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {
Layout::Builtin(Builtin::Int64) => {
self.build_num_mul_i64(sym, &args[0], &args[1])
}
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
debug_assert_eq!(
2,
args.len(),
"NumMul: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NumMul: expected all arguments of to have the same layout"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumMul: expected to have the same argument and return layout"
);
self.build_num_mul(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumPowInt => self.build_fn_call(
sym,
bitcode::NUM_POW_INT.to_string(),
args,
&[*layout, *layout],
layout,
arg_layouts,
ret_layout,
),
LowLevel::NumSub => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {
Layout::Builtin(Builtin::Int64) => {
self.build_num_sub_i64(sym, &args[0], &args[1])
}
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
debug_assert_eq!(
2,
args.len(),
"NumSub: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NumSub: expected all arguments of to have the same layout"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumSub: expected to have the same argument and return layout"
);
self.build_num_sub(sym, &args[0], &args[1], ret_layout)
}
LowLevel::Eq => {
debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument");
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"Eq: expected all arguments of to have the same layout"
);
debug_assert_eq!(
Layout::Builtin(Builtin::Int1),
*ret_layout,
"Eq: expected to have return layout of type I1"
);
self.build_eq(sym, &args[0], &args[1], &arg_layouts[0])
}
LowLevel::Eq => match layout {
Layout::Builtin(Builtin::Int1) => self.build_eq_i64(sym, &args[0], &args[1]),
// Should we panic?
x => Err(format!("wrong layout, {:?}, for LowLevel::Eq", x)),
},
LowLevel::NumRound => self.build_fn_call(
sym,
bitcode::NUM_ROUND.to_string(),
args,
&[Layout::Builtin(Builtin::Float64)],
layout,
arg_layouts,
ret_layout,
),
x => Err(format!("low level, {:?}. is not yet implemented", x)),
}
@ -328,54 +427,50 @@ where
ret_layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_abs_i64 stores the absolute value of src into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>;
/// build_num_abs stores the absolute value of src into dst.
fn build_num_abs(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_abs_f64 stores the absolute value of src into dst.
/// It only deals with inputs and outputs of f64 type.
fn build_num_abs_f64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>;
/// build_num_add_i64 stores the sum of src1 and src2 into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_num_add_i64(
/// build_num_add stores the sum of src1 and src2 into dst.
fn build_num_add(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_add_f64 stores the sum of src1 and src2 into dst.
/// It only deals with inputs and outputs of f64 type.
fn build_num_add_f64(
/// build_num_mul stores `src1 * src2` into dst.
fn build_num_mul(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_mul_i64 stores `src1 * src2` into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_num_mul_i64(
/// build_num_sub stores the `src1 - src2` difference into dst.
fn build_num_sub(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_sub_i64 stores the `src1 - src2` difference into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_num_sub_i64(
/// build_eq stores the result of `src1 == src2` into dst.
fn build_eq(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String>;
/// build_eq_i64 stores the result of `src1 == src2` into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_eq_i64(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol) -> Result<(), String>;
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
@ -434,6 +529,29 @@ where
/// last_seen_map gets the map from symbol to when it is last seen in the function.
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>>;
/// set_layout_map sets the layout for a specific symbol.
fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) -> Result<(), String> {
if let Some(x) = self.layout_map().insert(sym, layout) {
// Layout map already contains the symbol. We should never need to overwrite.
// If the layout is not the same, that is a bug.
// There is always an old layout value and this dereference is safe.
let old_layout = unsafe { *x };
if old_layout != *layout {
Err(format!(
"Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}",
sym, layout, old_layout
))
} else {
Ok(())
}
} else {
Ok(())
}
}
/// layout_map gets the map from symbol to layout.
fn layout_map(&mut self) -> &mut MutMap<Symbol, *const Layout<'a>>;
fn create_free_map(&mut self) {
let mut free_map = MutMap::default();
let arena = self.env().arena;

View File

@ -1,3 +1,4 @@
use std::convert::TryFrom;
use std::path::Path;
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
@ -52,6 +53,7 @@ use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{BranchInfo, CallType, EntryPoint, JoinPointId, ModifyRc, OptLevel, ProcLayout};
use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, UnionLayout};
use target_lexicon::{Architecture, OperatingSystem, Triple};
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
/// output in debug builds, but we don't want it to print to stdout in release builds!
@ -383,16 +385,34 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
}
pub fn module_from_builtins<'ctx>(
target: &target_lexicon::Triple,
ctx: &'ctx Context,
module_name: &str,
ptr_bytes: u32,
) -> Module<'ctx> {
// In the build script for the builtins module,
// we compile the builtins into LLVM bitcode
let bitcode_bytes: &[u8] = match ptr_bytes {
8 => include_bytes!("../../../builtins/bitcode/builtins-64bit.bc"),
4 => include_bytes!("../../../builtins/bitcode/builtins-32bit.bc"),
_ => unreachable!(),
// In the build script for the builtins module, we compile the builtins into LLVM bitcode
let bitcode_bytes: &[u8] = if target == &target_lexicon::Triple::host() {
include_bytes!("../../../builtins/bitcode/builtins-host.bc")
} else {
match target {
Triple {
architecture: Architecture::Wasm32,
..
} => {
include_bytes!("../../../builtins/bitcode/builtins-wasm32.bc")
}
Triple {
architecture: Architecture::X86_32(_),
operating_system: OperatingSystem::Linux,
..
} => {
include_bytes!("../../../builtins/bitcode/builtins-i386.bc")
}
_ => panic!(
"The zig builtins are not currently built for this target: {:?}",
target
),
}
};
let memory_buffer = MemoryBuffer::create_from_memory_range(bitcode_bytes, module_name);
@ -1705,7 +1725,11 @@ pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
pointer: PointerValue<'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();
@ -1724,11 +1748,17 @@ pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>(
) -> PointerValue<'ctx> {
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 mask = {
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")
};
@ -3101,21 +3131,6 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
.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>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
@ -3668,14 +3683,26 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
*param = builder.build_load(param.into_pointer_value(), "load_param");
}
let call_result = set_jump_and_catch_long_jump(
env,
function_value,
evaluator,
evaluator.get_call_conventions(),
closure_data,
result_type,
);
let call_result = if env.is_gen_test {
set_jump_and_catch_long_jump(
env,
function_value,
evaluator,
evaluator.get_call_conventions(),
closure_data,
result_type,
)
} else {
let call = env
.builder
.build_call(evaluator, closure_data, "call_function");
call.set_call_convention(evaluator.get_call_conventions());
let call_result = call.try_as_basic_value().left().unwrap();
make_good_roc_result(env, call_result)
};
builder.build_store(output, call_result);
@ -4447,6 +4474,11 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
}
}
// TODO: Fix me! I should be different in tests vs. user code!
fn expect_failed() {
panic!("An expectation failed!");
}
#[allow(clippy::too_many_arguments)]
fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -4457,6 +4489,7 @@ fn run_low_level<'a, 'ctx, 'env>(
op: LowLevel,
args: &[Symbol],
update_mode: Option<UpdateMode>,
// expect_failed: *const (),
) -> BasicValueEnum<'ctx> {
use LowLevel::*;
@ -4731,7 +4764,7 @@ fn run_low_level<'a, 'ctx, 'env>(
complex_bitcast(
env.builder,
list.into(),
env.context.i128_type().into(),
env.str_list_c_abi().into(),
"to_i128",
),
position,
@ -4749,7 +4782,7 @@ fn run_low_level<'a, 'ctx, 'env>(
complex_bitcast(
env.builder,
list.into(),
env.context.i128_type().into(),
env.str_list_c_abi().into(),
"to_i128",
),
position,
@ -5173,9 +5206,36 @@ fn run_low_level<'a, 'ctx, 'env>(
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);
@ -6028,7 +6088,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_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()]),
NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]),
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */

View File

@ -47,8 +47,6 @@ pub fn dict_len<'a, 'ctx, 'env>(
scope: &Scope<'a, 'ctx>,
dict_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let (_, dict_layout) = load_symbol_and_layout(scope, &dict_symbol);
match dict_layout {
@ -56,13 +54,17 @@ pub fn dict_len<'a, 'ctx, 'env>(
// let dict_as_int = dict_symbol_to_i128(env, scope, dict_symbol);
let dict_as_zig_dict = dict_symbol_to_zig_dict(env, scope, dict_symbol);
call_bitcode_fn(
let length_i64 = call_bitcode_fn(
env,
&[pass_dict_c_abi(env, dict_as_zig_dict.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),
}
}

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<'_, '_, '_>) {

View File

@ -1,8 +1,8 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I32,
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::convert::{basic_type_from_layout, ptr_int};
@ -158,12 +158,14 @@ impl<'ctx> PointerToRefcount<'ctx> {
}
pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) {
let alignment = layout
.allocation_alignment_bytes(env.ptr_bytes)
.max(env.ptr_bytes);
let context = env.context;
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let alignment = layout.alignment_bytes(env.ptr_bytes).max(env.ptr_bytes);
let fn_name = &format!("decrement_refcounted_ptr_{}", alignment);
let function = match env.module.get_function(fn_name) {
@ -212,127 +214,28 @@ impl<'ctx> PointerToRefcount<'ctx> {
) {
let builder = env.builder;
let ctx = env.context;
let refcount_type = ptr_int(ctx, env.ptr_bytes);
let entry = ctx.append_basic_block(parent, "entry");
builder.position_at_end(entry);
debug_info_init!(env, parent);
let refcount_ptr = {
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 alignment = env.context.i32_type().const_int(alignment as _, false);
let refcount = refcount_ptr.get_refcount(env);
let is_static_allocation = builder.build_int_compare(
IntPredicate::EQ,
refcount,
env.ptr_int().const_zero(),
"is_static_allocation",
call_void_bitcode_fn(
env,
&[
env.builder.build_bitcast(
parent.get_nth_param(0).unwrap(),
env.ptr_int().ptr_type(AddressSpace::Generic),
"foo",
),
alignment.into(),
],
roc_builtins::bitcode::UTILS_DECREF,
);
// build blocks
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_intrinsic = match env.ptr_bytes {
8 => LLVM_SADD_WITH_OVERFLOW_I64,
4 => LLVM_SADD_WITH_OVERFLOW_I32,
_ => unreachable!(),
};
let add_with_overflow;
{
builder.position_at_end(branch_block);
add_with_overflow = env
.call_intrinsic(
add_intrinsic,
&[
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);
}
builder.build_return(None);
}
}
@ -1214,11 +1117,11 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
debug_assert!(arg_val.is_pointer_value());
let current_tag_id = get_tag_id(env, fn_val, &union_layout, arg_val);
let value_ptr = tag_pointer_clear_tag_id(env, arg_val.into_pointer_value());
// to increment/decrement the cons-cell itself
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
let call_mode = mode_to_call_mode(fn_val, mode);
let value_ptr = if union_layout.stores_tag_id_in_pointer(env.ptr_bytes) {
tag_pointer_clear_tag_id(env, arg_val.into_pointer_value())
} else {
arg_val.into_pointer_value()
};
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
@ -1241,6 +1144,10 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env.builder.position_at_end(should_recurse_block);
// to increment/decrement the cons-cell itself
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
let call_mode = mode_to_call_mode(fn_val, mode);
let layout = Layout::Union(union_layout);
match mode {

View File

@ -721,6 +721,12 @@ pub struct MonomorphizedModule<'a> {
pub timings: MutMap<ModuleId, ModuleTiming>,
}
impl<'a> MonomorphizedModule<'a> {
pub fn total_problems(&self) -> usize {
self.can_problems.len() + self.type_problems.len() + self.mono_problems.len()
}
}
#[derive(Debug, Default)]
pub struct VariablySizedLayouts<'a> {
rigids: MutMap<Lowercase, Layout<'a>>,
@ -4293,7 +4299,7 @@ where
}
fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
let src_lines: Vec<&str> = Vec::new();
@ -4324,6 +4330,7 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
filename: "UNKNOWN.roc".into(),
doc,
title: "FILE NOT FOUND".to_string(),
severity: Severity::RuntimeError,
}
}
io::ErrorKind::PermissionDenied => {
@ -4340,7 +4347,8 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "PERMISSION DENIED".to_string(),
title: "FILE PERMISSION DENIED".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -4356,6 +4364,7 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
filename: "UNKNOWN.roc".into(),
doc,
title: "FILE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
};
@ -4401,7 +4410,7 @@ fn to_parse_problem_report<'a>(
}
fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
use PlatformPath::*;
@ -4426,6 +4435,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
severity: Severity::RuntimeError,
}
}
RootIsInterface => {
@ -4441,6 +4451,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
severity: Severity::RuntimeError,
}
}
RootIsPkgConfig => {
@ -4456,6 +4467,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
severity: Severity::RuntimeError,
}
}
}

View File

@ -386,6 +386,32 @@ impl<'a> UnionLayout<'a> {
UnionLayout::NullableWrapped { .. } | UnionLayout::NullableUnwrapped { .. } => true,
}
}
fn tags_alignment_bytes(tags: &[&[Layout]], pointer_size: u32) -> u32 {
tags.iter()
.map(|fields| Layout::Struct(fields).alignment_bytes(pointer_size))
.max()
.unwrap_or(0)
}
pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 {
let allocation = match self {
UnionLayout::NonRecursive(_) => unreachable!("not heap-allocated"),
UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, pointer_size),
UnionLayout::NonNullableUnwrapped(fields) => {
Layout::Struct(fields).alignment_bytes(pointer_size)
}
UnionLayout::NullableWrapped { other_tags, .. } => {
Self::tags_alignment_bytes(other_tags, pointer_size)
}
UnionLayout::NullableUnwrapped { other_fields, .. } => {
Layout::Struct(other_fields).alignment_bytes(pointer_size)
}
};
// because we store a refcount, the alignment must be at least the size of a pointer
allocation.max(pointer_size)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -898,6 +924,15 @@ impl<'a> Layout<'a> {
}
}
pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 {
match self {
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(pointer_size),
Layout::Struct(_) => unreachable!("not heap-allocated"),
Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(pointer_size),
Layout::RecursivePointer => unreachable!("should be looked up to get an actual layout"),
}
}
pub fn is_refcounted(&self) -> bool {
use self::Builtin::*;
use Layout::*;
@ -1187,6 +1222,34 @@ impl<'a> Builtin<'a> {
.append(value_layout.to_doc(alloc, Parens::InTypeParam)),
}
}
pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 {
let allocation = match self {
Builtin::Int128
| Builtin::Int64
| Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize
| Builtin::Decimal
| Builtin::Float128
| Builtin::Float64
| Builtin::Float32
| Builtin::Float16 => unreachable!("not heap-allocated"),
Builtin::Str => pointer_size,
Builtin::Dict(k, v) => k
.alignment_bytes(pointer_size)
.max(v.alignment_bytes(pointer_size)),
Builtin::Set(k) => k.alignment_bytes(pointer_size),
Builtin::List(e) => e.alignment_bytes(pointer_size),
Builtin::EmptyStr | Builtin::EmptyList | Builtin::EmptyDict | Builtin::EmptySet => {
unreachable!("not heap-allocated")
}
};
allocation.max(pointer_size)
}
}
fn layout_from_flat_type<'a>(

View File

@ -3,23 +3,43 @@ use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_parse::parser::{Col, Row};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region;
use roc_region::all::{Located, Region};
use std::path::PathBuf;
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use ven_pretty::DocAllocator;
const SYNTAX_PROBLEM: &str = "SYNTAX PROBLEM";
const NAMING_PROBLEM: &str = "NAMING PROBLEM";
const UNRECOGNIZED_NAME: &str = "UNRECOGNIZED NAME";
const UNUSED_DEF: &str = "UNUSED DEFINITION";
const UNUSED_IMPORT: &str = "UNUSED IMPORT";
const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER";
const UNUSED_ARG: &str = "UNUSED ARGUMENT";
const MISSING_DEFINITION: &str = "MISSING_DEFINITION";
const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME";
const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME";
const INVALID_UNICODE: &str = "INVALID UNICODE";
const CIRCULAR_DEF: &str = "CIRCULAR DEFINITION";
const DUPLICATE_NAME: &str = "DUPLICATE NAME";
const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED";
const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED";
pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>,
filename: PathBuf,
problem: Problem,
) -> Report<'b> {
let doc = match problem {
let doc;
let title;
let severity;
match problem {
Problem::UnusedDef(symbol, region) => {
let line =
r#" then remove it so future readers of your code don't wonder why it is there."#;
alloc.stack(vec![
doc = alloc.stack(vec![
alloc
.symbol_unqualified(symbol)
.append(alloc.reflow(" is not used anywhere in your code.")),
@ -28,36 +48,49 @@ pub fn can_problem<'b>(
.reflow("If you didn't intend on using ")
.append(alloc.symbol_unqualified(symbol))
.append(alloc.reflow(line)),
])
]);
title = UNUSED_DEF.to_string();
severity = Severity::Warning;
}
Problem::UnusedImport(module_id, region) => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("Nothing from "),
alloc.module(module_id),
alloc.reflow(" is used in this module."),
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Since "),
alloc.module(module_id),
alloc.reflow(" isn't used, you don't need to import it."),
]),
]);
title = UNUSED_IMPORT.to_string();
severity = Severity::Warning;
}
Problem::ExposedButNotDefined(symbol) => {
doc = alloc.stack(vec![
alloc.symbol_unqualified(symbol).append(
alloc.reflow(" is listed as exposed, but it isn't defined in this module."),
),
alloc
.reflow("You can fix this by adding a definition for ")
.append(alloc.symbol_unqualified(symbol))
.append(alloc.reflow(", or by removing it from "))
.append(alloc.keyword("exposes"))
.append(alloc.reflow(".")),
]);
title = MISSING_DEFINITION.to_string();
severity = Severity::RuntimeError;
}
Problem::UnusedImport(module_id, region) => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("Nothing from "),
alloc.module(module_id),
alloc.reflow(" is used in this module."),
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Since "),
alloc.module(module_id),
alloc.reflow(" isn't used, you don't need to import it."),
]),
]),
Problem::ExposedButNotDefined(symbol) => alloc.stack(vec![
alloc.symbol_unqualified(symbol).append(
alloc.reflow(" is listed as exposed, but it isn't defined in this module."),
),
alloc
.reflow("You can fix this by adding a definition for ")
.append(alloc.symbol_unqualified(symbol))
.append(alloc.reflow(", or by removing it from "))
.append(alloc.keyword("exposes"))
.append(alloc.reflow(".")),
]),
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";
alloc.stack(vec![
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.symbol_unqualified(closure_symbol),
alloc.reflow(" doesn't use "),
@ -76,10 +109,13 @@ pub fn can_problem<'b>(
alloc.symbol_unqualified(argument_symbol),
alloc.reflow(line),
]),
])
]);
title = UNUSED_ARG.to_string();
severity = Severity::Warning;
}
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => alloc
.stack(vec![
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => {
doc = alloc.stack(vec![
if left_bin_op.value == right_bin_op.value {
alloc.concat(vec![
alloc.reflow("Using more than one "),
@ -102,11 +138,20 @@ pub fn can_problem<'b>(
])
},
alloc.region(region),
]),
Problem::UnsupportedPattern(BadPattern::UnderscoreInDef, region) => alloc.stack(vec![
alloc.reflow("Underscore patterns are not allowed in definitions"),
alloc.region(region),
]),
]);
title = SYNTAX_PROBLEM.to_string();
severity = Severity::RuntimeError;
}
Problem::UnsupportedPattern(BadPattern::UnderscoreInDef, region) => {
doc = alloc.stack(vec![
alloc.reflow("Underscore patterns are not allowed in definitions"),
alloc.region(region),
]);
title = SYNTAX_PROBLEM.to_string();
severity = Severity::RuntimeError;
}
Problem::UnsupportedPattern(BadPattern::Unsupported(pattern_type), region) => {
use roc_parse::pattern::PatternType::*;
@ -127,84 +172,98 @@ pub fn can_problem<'b>(
alloc.reflow(" instead."),
];
alloc.stack(vec![
doc = alloc.stack(vec![
alloc
.reflow("This pattern is not allowed in ")
.append(alloc.reflow(this_thing)),
alloc.region(region),
alloc.concat(suggestion),
])
]);
title = SYNTAX_PROBLEM.to_string();
severity = Severity::RuntimeError;
}
Problem::ShadowingInAnnotation {
original_region,
shadow,
} => pretty_runtime_error(
alloc,
RuntimeError::Shadowing {
original_region,
shadow,
},
),
Problem::CyclicAlias(symbol, region, others) => {
let (doc, title) = crate::error::r#type::cyclic_alias(alloc, symbol, region, others);
} => {
doc = report_shadowing(alloc, original_region, shadow);
return Report {
title,
filename,
doc,
};
title = DUPLICATE_NAME.to_string();
severity = Severity::RuntimeError;
}
Problem::CyclicAlias(symbol, region, others) => {
let answer = crate::error::r#type::cyclic_alias(alloc, symbol, region, others);
doc = answer.0;
title = answer.1;
severity = Severity::RuntimeError;
}
Problem::PhantomTypeArgument {
alias,
variable_region,
variable_name,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The "),
alloc.type_variable(variable_name),
alloc.reflow(" type variable is not used in the "),
alloc.symbol_unqualified(alias),
alloc.reflow(" alias definition:"),
]),
alloc.region(variable_region),
alloc.reflow("Roc does not allow unused type parameters!"),
// TODO add link to this guide section
alloc.tip().append(alloc.reflow(
"If you want an unused type parameter (a so-called \"phantom type\"), \
read the guide section on phantom data.",
)),
]),
Problem::BadRecursion(entries) => to_circular_def_doc(alloc, &entries),
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The "),
alloc.type_variable(variable_name),
alloc.reflow(" type parameter is not used in the "),
alloc.symbol_unqualified(alias),
alloc.reflow(" alias definition:"),
]),
alloc.region(variable_region),
alloc.reflow("Roc does not allow unused type alias parameters!"),
// TODO add link to this guide section
alloc.tip().append(alloc.reflow(
"If you want an unused type parameter (a so-called \"phantom type\"), \
read the guide section on phantom values.",
)),
]);
title = UNUSED_ALIAS_PARAM.to_string();
severity = Severity::RuntimeError;
}
Problem::BadRecursion(entries) => {
doc = to_circular_def_doc(alloc, &entries);
title = CIRCULAR_DEF.to_string();
severity = Severity::RuntimeError;
}
Problem::DuplicateRecordFieldValue {
field_name,
field_region,
record_region,
replaced_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record defines the "),
alloc.record_field(field_name.clone()),
alloc.reflow(" field twice!"),
]),
alloc.region_all_the_things(
record_region,
replaced_region,
field_region,
Annotation::Error,
),
alloc.reflow(r"In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.record_field(field_name),
alloc.reflow(" definitions from this record."),
]),
]),
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record defines the "),
alloc.record_field(field_name.clone()),
alloc.reflow(" field twice!"),
]),
alloc.region_all_the_things(
record_region,
replaced_region,
field_region,
Annotation::Error,
),
alloc.reflow(r"In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.record_field(field_name),
alloc.reflow(" definitions from this record."),
]),
]);
title = DUPLICATE_FIELD_NAME.to_string();
severity = Severity::Warning;
}
Problem::InvalidOptionalValue {
field_name,
field_region,
@ -223,120 +282,164 @@ pub fn can_problem<'b>(
field_region,
record_region,
replaced_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record type defines the "),
alloc.record_field(field_name.clone()),
alloc.reflow(" field twice!"),
]),
alloc.region_all_the_things(
record_region,
replaced_region,
field_region,
Annotation::Error,
),
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.record_field(field_name),
alloc.reflow(" definitions from this record type."),
]),
]),
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record type defines the "),
alloc.record_field(field_name.clone()),
alloc.reflow(" field twice!"),
]),
alloc.region_all_the_things(
record_region,
replaced_region,
field_region,
Annotation::Error,
),
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.record_field(field_name),
alloc.reflow(" definitions from this record type."),
]),
]);
title = DUPLICATE_FIELD_NAME.to_string();
severity = Severity::Warning;
}
Problem::DuplicateTag {
tag_name,
tag_union_region,
tag_region,
replaced_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This tag union type defines the "),
alloc.tag_name(tag_name.clone()),
alloc.reflow(" tag twice!"),
]),
alloc.region_all_the_things(
tag_union_region,
replaced_region,
tag_region,
Annotation::Error,
),
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
tag_union_region,
tag_region,
tag_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.tag_name(tag_name),
alloc.reflow(" definitions from this tag union type."),
]),
]),
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This tag union type defines the "),
alloc.tag_name(tag_name.clone()),
alloc.reflow(" tag twice!"),
]),
alloc.region_all_the_things(
tag_union_region,
replaced_region,
tag_region,
Annotation::Error,
),
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
tag_union_region,
tag_region,
tag_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.tag_name(tag_name),
alloc.reflow(" definitions from this tag union type."),
]),
]);
title = DUPLICATE_TAG_NAME.to_string();
severity = Severity::Warning;
}
Problem::SignatureDefMismatch {
ref annotation_pattern,
ref def_pattern,
} => alloc.stack(vec![
alloc.reflow("This annotation does not match the definition immediately following it:"),
alloc.region(Region::span_across(annotation_pattern, def_pattern)),
alloc.reflow("Is it a typo? If not, put either a newline or comment between them."),
]),
Problem::InvalidAliasRigid { alias_name, region } => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This pattern in the definition of "),
alloc.symbol_unqualified(alias_name),
alloc.reflow(" is not what I expect:"),
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Only type variables like "),
alloc.type_variable("a".into()),
alloc.reflow(" or "),
alloc.type_variable("value".into()),
alloc.reflow(" can occur in this position."),
]),
]),
Problem::InvalidHexadecimal(region) => alloc.stack(vec![
alloc.reflow("This unicode code point is invalid:"),
alloc.region(region),
alloc.concat(vec![
alloc.reflow(r"I was expecting a hexadecimal number, like "),
alloc.parser_suggestion("\\u(1100)"),
alloc.reflow(" or "),
alloc.parser_suggestion("\\u(00FF)"),
alloc.text("."),
]),
alloc.reflow(r"Learn more about working with unicode in roc at TODO"),
]),
Problem::InvalidUnicodeCodePt(region) => alloc.stack(vec![
alloc.reflow("This unicode code point is invalid:"),
alloc.region(region),
alloc.reflow("Learn more about working with unicode in roc at TODO"),
]),
Problem::InvalidInterpolation(region) => alloc.stack(vec![
alloc.reflow("This string interpolation is invalid:"),
alloc.region(region),
alloc.concat(vec![
alloc.reflow(r"I was expecting an identifier, like "),
alloc.parser_suggestion("\\u(message)"),
alloc.reflow(" or "),
alloc.parser_suggestion("\\u(LoremIpsum.text)"),
alloc.text("."),
]),
alloc.reflow(r"Learn more about string interpolation at TODO"),
]),
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
} => {
doc = alloc.stack(vec![
alloc.reflow(
"This annotation does not match the definition immediately following it:",
),
alloc.region(Region::span_across(annotation_pattern, def_pattern)),
alloc.reflow("Is it a typo? If not, put either a newline or comment between them."),
]);
title = NAMING_PROBLEM.to_string();
severity = Severity::RuntimeError;
}
Problem::InvalidAliasRigid { alias_name, region } => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This pattern in the definition of "),
alloc.symbol_unqualified(alias_name),
alloc.reflow(" is not what I expect:"),
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Only type variables like "),
alloc.type_variable("a".into()),
alloc.reflow(" or "),
alloc.type_variable("value".into()),
alloc.reflow(" can occur in this position."),
]),
]);
title = SYNTAX_PROBLEM.to_string();
severity = Severity::RuntimeError;
}
Problem::InvalidHexadecimal(region) => {
doc = alloc.stack(vec![
alloc.reflow("This unicode code point is invalid:"),
alloc.region(region),
alloc.concat(vec![
alloc.reflow(r"I was expecting a hexadecimal number, like "),
alloc.parser_suggestion("\\u(1100)"),
alloc.reflow(" or "),
alloc.parser_suggestion("\\u(00FF)"),
alloc.text("."),
]),
alloc.reflow(r"Learn more about working with unicode in roc at TODO"),
]);
title = INVALID_UNICODE.to_string();
severity = Severity::RuntimeError;
}
Problem::InvalidUnicodeCodePt(region) => {
doc = alloc.stack(vec![
alloc.reflow("This unicode code point is invalid:"),
alloc.region(region),
alloc.reflow("Learn more about working with unicode in roc at TODO"),
]);
title = INVALID_UNICODE.to_string();
severity = Severity::RuntimeError;
}
Problem::InvalidInterpolation(region) => {
doc = alloc.stack(vec![
alloc.reflow("This string interpolation is invalid:"),
alloc.region(region),
alloc.concat(vec![
alloc.reflow(r"I was expecting an identifier, like "),
alloc.parser_suggestion("\\u(message)"),
alloc.reflow(" or "),
alloc.parser_suggestion("\\u(LoremIpsum.text)"),
alloc.text("."),
]),
alloc.reflow(r"Learn more about string interpolation at TODO"),
]);
title = SYNTAX_PROBLEM.to_string();
severity = Severity::RuntimeError;
}
Problem::RuntimeError(runtime_error) => {
let answer = pretty_runtime_error(alloc, runtime_error);
doc = answer.0;
title = answer.1.to_string();
severity = Severity::RuntimeError;
}
};
Report {
title: "SYNTAX PROBLEM".to_string(),
title,
filename,
doc,
severity,
}
}
@ -353,6 +456,7 @@ fn to_invalid_optional_value_report<'b>(
title: "BAD OPTIONAL VALUE".to_string(),
filename,
doc,
severity: Severity::RuntimeError,
}
}
@ -666,44 +770,60 @@ where
chomped
}
fn report_shadowing<'b>(
alloc: &'b RocDocAllocator<'b>,
original_region: Region,
shadow: Located<Ident>,
) -> RocDocBuilder<'b> {
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
alloc.stack(vec![
alloc
.text("The ")
.append(alloc.ident(shadow.value))
.append(alloc.reflow(" name is first defined here:")),
alloc.region(original_region),
alloc.reflow("But then it's defined a second time here:"),
alloc.region(shadow.region),
alloc.reflow(line),
])
}
fn pretty_runtime_error<'b>(
alloc: &'b RocDocAllocator<'b>,
runtime_error: RuntimeError,
) -> RocDocBuilder<'b> {
) -> (RocDocBuilder<'b>, &'static str) {
let doc;
let title;
match runtime_error {
RuntimeError::VoidValue => {
// is used to communicate to the compiler that
// a branch is unreachable; this should never reach a user
unreachable!("")
unreachable!("");
}
RuntimeError::UnresolvedTypeVar | RuntimeError::ErroneousType => {
// only generated during layout generation
unreachable!("")
unreachable!("");
}
RuntimeError::Shadowing {
original_region,
shadow,
} => {
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
alloc.stack(vec![
alloc
.text("The ")
.append(alloc.ident(shadow.value))
.append(alloc.reflow(" name is first defined here:")),
alloc.region(original_region),
alloc.reflow("But then it's defined a second time here:"),
alloc.region(shadow.region),
alloc.reflow(line),
])
doc = report_shadowing(alloc, original_region, shadow);
title = DUPLICATE_NAME;
}
RuntimeError::LookupNotInScope(loc_name, options) => {
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
doc = not_found(alloc, loc_name.region, &loc_name.value, "value", options);
title = UNRECOGNIZED_NAME;
}
RuntimeError::CircularDef(entries) => {
doc = to_circular_def_doc(alloc, &entries);
title = CIRCULAR_DEF;
}
RuntimeError::CircularDef(entries) => to_circular_def_doc(alloc, &entries),
RuntimeError::MalformedPattern(problem, region) => {
use roc_parse::ast::Base;
use roc_problem::can::MalformedPatternProblem::*;
@ -716,7 +836,10 @@ fn pretty_runtime_error<'b>(
MalformedBase(Base::Octal) => " octal integer ",
MalformedBase(Base::Decimal) => " integer ",
BadIdent(bad_ident) => {
return to_bad_ident_pattern_report(alloc, bad_ident, region)
title = NAMING_PROBLEM;
doc = to_bad_ident_pattern_report(alloc, bad_ident, region);
return (doc, title);
}
Unknown => " ",
QualifiedIdentifier => " qualified ",
@ -732,7 +855,7 @@ fn pretty_runtime_error<'b>(
),
};
alloc.stack(vec![
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This"),
alloc.text(name),
@ -740,7 +863,9 @@ fn pretty_runtime_error<'b>(
]),
alloc.region(region),
tip,
])
]);
title = SYNTAX_PROBLEM;
}
RuntimeError::UnsupportedPattern(_) => {
todo!("unsupported patterns are currently not parsed!")
@ -749,42 +874,58 @@ fn pretty_runtime_error<'b>(
module_name,
ident,
region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The "),
alloc.module_name(module_name),
alloc.reflow(" module does not expose a "),
alloc.string(ident.to_string()),
alloc.reflow(" value:"),
]),
alloc.region(region),
]),
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The "),
alloc.module_name(module_name),
alloc.reflow(" module does not expose a "),
alloc.string(ident.to_string()),
alloc.reflow(" value:"),
]),
alloc.region(region),
]);
title = VALUE_NOT_EXPOSED;
}
RuntimeError::ModuleNotImported {
module_name,
imported_modules,
region,
} => module_not_found(alloc, region, &module_name, imported_modules),
} => {
doc = module_not_found(alloc, region, &module_name, imported_modules);
title = MODULE_NOT_IMPORTED;
}
RuntimeError::InvalidPrecedence(_, _) => {
// do nothing, reported with PrecedenceProblem
unreachable!()
unreachable!();
}
RuntimeError::MalformedIdentifier(_box_str, bad_ident, surroundings) => {
to_bad_ident_expr_report(alloc, bad_ident, surroundings)
doc = to_bad_ident_expr_report(alloc, bad_ident, surroundings);
title = SYNTAX_PROBLEM;
}
RuntimeError::MalformedTypeName(_box_str, surroundings) => {
doc = alloc.stack(vec![
alloc.reflow(r"I am confused by this type name:"),
alloc.region(surroundings),
alloc.concat(vec![
alloc.reflow("Type names start with an uppercase letter, "),
alloc.reflow("and can optionally be qualified by a module name, like "),
alloc.parser_suggestion("Bool"),
alloc.reflow(" or "),
alloc.parser_suggestion("Http.Request.Request"),
alloc.reflow("."),
]),
]);
title = SYNTAX_PROBLEM;
}
RuntimeError::MalformedClosure(_) => {
todo!("");
}
RuntimeError::MalformedTypeName(_box_str, surroundings) => alloc.stack(vec![
alloc.reflow(r"I am confused by this type name:"),
alloc.region(surroundings),
alloc.concat(vec![
alloc.reflow("Type names start with an uppercase letter, "),
alloc.reflow("and can optionally be qualified by a module name, like "),
alloc.parser_suggestion("Bool"),
alloc.reflow(" or "),
alloc.parser_suggestion("Http.Request.Request"),
alloc.reflow("."),
]),
]),
RuntimeError::MalformedClosure(_) => todo!(""),
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
| RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => {
let tip = alloc
@ -797,7 +938,7 @@ fn pretty_runtime_error<'b>(
"small"
};
alloc.stack(vec![
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This float literal is too "),
alloc.text(big_or_small),
@ -812,14 +953,16 @@ fn pretty_runtime_error<'b>(
alloc.text(format!("{:e}", f64::MAX)),
]),
tip,
])
]);
title = SYNTAX_PROBLEM;
}
RuntimeError::InvalidFloat(FloatErrorKind::Error, region, _raw_str) => {
let tip = alloc
.tip()
.append(alloc.reflow("Learn more about number literals at TODO"));
alloc.stack(vec![
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This float literal contains an invalid digit:"),
]),
@ -828,7 +971,9 @@ fn pretty_runtime_error<'b>(
alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"),
]),
tip,
])
]);
title = SYNTAX_PROBLEM;
}
RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str)
| RuntimeError::InvalidInt(error @ IntErrorKind::Empty, base, region, _raw_str) => {
@ -871,7 +1016,7 @@ fn pretty_runtime_error<'b>(
.tip()
.append(alloc.reflow("Learn more about number literals at TODO"));
alloc.stack(vec![
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This "),
alloc.text(name),
@ -887,7 +1032,9 @@ fn pretty_runtime_error<'b>(
alloc.text("."),
]),
tip,
])
]);
title = SYNTAX_PROBLEM;
}
RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str)
| RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => {
@ -901,7 +1048,7 @@ fn pretty_runtime_error<'b>(
.tip()
.append(alloc.reflow("Learn more about number literals at TODO"));
alloc.stack(vec![
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This integer literal is too "),
alloc.text(big_or_small),
@ -910,21 +1057,36 @@ fn pretty_runtime_error<'b>(
alloc.region(region),
alloc.reflow("Roc uses signed 64-bit integers, allowing values between 9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."),
tip,
])
]);
title = SYNTAX_PROBLEM;
}
RuntimeError::InvalidOptionalValue {
field_name,
field_region,
record_region,
} => to_invalid_optional_value_report_help(alloc, field_name, field_region, record_region),
RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This expression cannot be updated"),
alloc.reflow(":"),
]),
alloc.region(region),
alloc.reflow("Only variables can be updated with record update syntax."),
]),
} => {
doc = to_invalid_optional_value_report_help(
alloc,
field_name,
field_region,
record_region,
);
title = SYNTAX_PROBLEM;
}
RuntimeError::InvalidRecordUpdate { region } => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This expression cannot be updated"),
alloc.reflow(":"),
]),
alloc.region(region),
alloc.reflow("Only variables can be updated with record update syntax."),
]);
title = SYNTAX_PROBLEM;
}
RuntimeError::InvalidHexadecimal(region) => {
todo!(
"TODO runtime error for an invalid hexadecimal number in a \\u(...) code point at region {:?}",
@ -949,12 +1111,20 @@ fn pretty_runtime_error<'b>(
RuntimeError::NonExhaustivePattern => {
unreachable!("not currently reported (but can blow up at runtime)")
}
RuntimeError::ExposedButNotDefined(symbol) => alloc.stack(vec![alloc
.symbol_unqualified(symbol)
.append(alloc.reflow(" was listed as exposed in "))
.append(alloc.module(symbol.module_id()))
.append(alloc.reflow(", but it was not defined anywhere in that module."))]),
RuntimeError::ExposedButNotDefined(symbol) => {
doc = alloc.stack(vec![alloc
.symbol_unqualified(symbol)
.append(alloc.reflow(" was listed as exposed in "))
.append(alloc.module(symbol.module_id()))
.append(
alloc.reflow(", but it was not defined anywhere in that module."),
)]);
title = MISSING_DEFINITION;
}
}
(doc, title)
}
fn to_circular_def_doc<'b>(

View File

@ -1,4 +1,4 @@
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use std::path::PathBuf;
use ven_pretty::DocAllocator;
@ -33,6 +33,7 @@ pub fn mono_problem<'b>(
filename,
title: "UNSAFE PATTERN".to_string(),
doc,
severity: Severity::RuntimeError,
}
}
BadDestruct => {
@ -56,6 +57,7 @@ pub fn mono_problem<'b>(
filename,
title: "UNSAFE PATTERN".to_string(),
doc,
severity: Severity::RuntimeError,
}
}
BadCase => {
@ -79,6 +81,7 @@ pub fn mono_problem<'b>(
filename,
title: "UNSAFE PATTERN".to_string(),
doc,
severity: Severity::RuntimeError,
}
}
},
@ -104,6 +107,7 @@ pub fn mono_problem<'b>(
filename,
title: "REDUNDANT PATTERN".to_string(),
doc,
severity: Severity::Warning,
}
}
}

View File

@ -2,7 +2,7 @@ use roc_parse::parser::{Col, ParseProblem, Row, SyntaxError};
use roc_region::all::Region;
use std::path::PathBuf;
use crate::report::{Report, RocDocAllocator, RocDocBuilder};
use crate::report::{Report, RocDocAllocator, RocDocBuilder, Severity};
use ven_pretty::DocAllocator;
pub fn parse_problem<'a>(
@ -75,6 +75,7 @@ fn to_syntax_report<'a>(
filename: filename.clone(),
doc,
title: "PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
};
let region = Region {
@ -95,6 +96,7 @@ fn to_syntax_report<'a>(
filename,
doc,
title: "PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
SyntaxError::ArgumentsBeforeEquals(region) => {
@ -107,6 +109,7 @@ fn to_syntax_report<'a>(
filename,
doc,
title: "PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
Unexpected(mut region) => {
@ -139,6 +142,7 @@ fn to_syntax_report<'a>(
filename,
doc,
title: "NOT END OF FILE".to_string(),
severity: Severity::RuntimeError,
}
}
SyntaxError::Eof(region) => {
@ -148,6 +152,7 @@ fn to_syntax_report<'a>(
filename,
doc,
title: "PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
SyntaxError::OutdentedTooFar => {
@ -157,6 +162,7 @@ fn to_syntax_report<'a>(
filename,
doc,
title: "PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
Type(typ) => to_type_report(alloc, filename, typ, 0, 0),
@ -235,6 +241,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: "ARGUMENTS BEFORE EQUALS".to_string(),
severity: Severity::RuntimeError,
}
}
@ -320,6 +327,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: "UNKNOWN OPERATOR".to_string(),
severity: Severity::RuntimeError,
}
}
@ -345,6 +353,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: "WEIRD IDENTIFIER".to_string(),
severity: Severity::RuntimeError,
}
}
@ -426,6 +435,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: title.to_string(),
severity: Severity::RuntimeError,
}
}
@ -453,6 +463,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: "MISSING FINAL EXPRESSION".to_string(),
severity: Severity::RuntimeError,
}
}
@ -483,6 +494,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: "SYNTAX PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
@ -505,6 +517,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: "ARGUMENTS BEFORE EQUALS".to_string(),
severity: Severity::RuntimeError,
}
}
@ -524,6 +537,7 @@ fn to_expr_report<'a>(
filename,
doc,
title: "BAD BACKPASSING ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
@ -564,6 +578,7 @@ fn to_lambda_report<'a>(
filename,
doc,
title: "WEIRD ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -585,6 +600,7 @@ fn to_lambda_report<'a>(
filename,
doc,
title: "MISSING ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -609,6 +625,7 @@ fn to_lambda_report<'a>(
filename,
doc,
title: "WEIRD ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -630,6 +647,7 @@ fn to_lambda_report<'a>(
filename,
doc,
title: "MISSING ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -653,6 +671,7 @@ fn to_lambda_report<'a>(
filename,
doc,
title: "UNFINISHED ARGUMENT LIST".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -673,6 +692,7 @@ fn to_lambda_report<'a>(
filename,
doc,
title: "MISSING ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -762,6 +782,7 @@ fn to_unfinished_lambda_report<'a>(
filename,
doc,
title: "UNFINISHED FUNCTION".to_string(),
severity: Severity::RuntimeError,
}
}
@ -824,6 +845,7 @@ fn to_str_report<'a>(
filename,
doc,
title: "WEIRD ESCAPE".to_string(),
severity: Severity::RuntimeError,
}
}
EString::CodePtOpen(row, col) | EString::CodePtEnd(row, col) => {
@ -849,6 +871,7 @@ fn to_str_report<'a>(
filename,
doc,
title: "WEIRD CODE POINT".to_string(),
severity: Severity::RuntimeError,
}
}
EString::FormatEnd(row, col) => {
@ -869,6 +892,7 @@ fn to_str_report<'a>(
filename,
doc,
title: "ENDLESS FORMAT".to_string(),
severity: Severity::RuntimeError,
}
}
EString::EndlessSingle(row, col) => {
@ -891,6 +915,7 @@ fn to_str_report<'a>(
filename,
doc,
title: "ENDLESS STRING".to_string(),
severity: Severity::RuntimeError,
}
}
EString::EndlessMulti(row, col) => {
@ -913,6 +938,7 @@ fn to_str_report<'a>(
filename,
doc,
title: "ENDLESS STRING".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -958,6 +984,7 @@ fn to_expr_in_parens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
EInParens::Open(row, col) | EInParens::IndentOpen(row, col) => {
@ -982,6 +1009,7 @@ fn to_expr_in_parens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -1031,6 +1059,7 @@ fn to_list_report<'a>(
filename,
doc,
title: "UNFINISHED LIST".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -1063,6 +1092,7 @@ fn to_list_report<'a>(
filename,
doc,
title: "UNFINISHED LIST".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -1090,6 +1120,7 @@ fn to_list_report<'a>(
filename,
doc,
title: "UNFINISHED LIST".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -1210,6 +1241,7 @@ fn to_unfinished_if_report<'a>(
filename,
doc,
title: "UNFINISHED IF".to_string(),
severity: Severity::RuntimeError,
}
}
@ -1243,6 +1275,7 @@ fn to_when_report<'a>(
filename,
doc,
title: "IF GUARD NO CONDITION".to_string(),
severity: Severity::RuntimeError,
}
}
_ => to_expr_report(
@ -1273,6 +1306,7 @@ fn to_when_report<'a>(
filename,
doc,
title: "MISSING ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
@ -1440,6 +1474,7 @@ fn to_unfinished_when_report<'a>(
filename,
doc,
title: "UNFINISHED WHEN".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -1477,6 +1512,7 @@ fn to_unexpected_arrow_report<'a>(
filename,
doc,
title: "UNEXPECTED ARROW".to_string(),
severity: Severity::RuntimeError,
}
}
@ -1552,6 +1588,7 @@ fn to_pattern_report<'a>(
filename,
doc,
title: "UNFINISHED PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
EPattern::Record(record, row, col) => {
@ -1593,6 +1630,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -1609,6 +1647,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -1633,6 +1672,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -1652,6 +1692,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -1676,6 +1717,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
Next::Other(Some(',')) => todo!(),
@ -1700,6 +1742,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "PROBLEM IN RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -1744,6 +1787,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
@ -1768,6 +1812,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "NEED MORE INDENTATION".to_string(),
severity: Severity::RuntimeError,
}
}
None => {
@ -1792,6 +1837,7 @@ fn to_precord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -1842,6 +1888,7 @@ fn to_pattern_in_parens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -1865,6 +1912,7 @@ fn to_pattern_in_parens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -1889,6 +1937,7 @@ fn to_pattern_in_parens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -1913,6 +1962,7 @@ fn to_pattern_in_parens_report<'a>(
filename,
doc,
title: "NEED MORE INDENTATION".to_string(),
severity: Severity::RuntimeError,
}
}
None => {
@ -1937,6 +1987,7 @@ fn to_pattern_in_parens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -1980,6 +2031,7 @@ fn to_type_report<'a>(
filename,
doc,
title: "DOUBLE COMMA".to_string(),
severity: Severity::RuntimeError,
}
}
_ => todo!(),
@ -2005,6 +2057,7 @@ fn to_type_report<'a>(
filename,
doc,
title: "UNFINISHED TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2022,6 +2075,7 @@ fn to_type_report<'a>(
filename,
doc,
title: "UNFINISHED TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2039,6 +2093,7 @@ fn to_type_report<'a>(
filename,
doc,
title: "UNFINISHED TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2056,6 +2111,7 @@ fn to_type_report<'a>(
filename,
doc,
title: "UNFINISHED INLINE ALIAS".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2072,6 +2128,7 @@ fn to_type_report<'a>(
filename,
doc,
title: "BAD TYPE VARIABLE".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2108,6 +2165,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -2128,6 +2186,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -2152,6 +2211,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -2171,6 +2231,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -2195,6 +2256,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
Next::Other(Some(',')) => todo!(),
@ -2219,6 +2281,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "PROBLEM IN RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -2251,6 +2314,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2275,6 +2339,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "NEED MORE INDENTATION".to_string(),
severity: Severity::RuntimeError,
}
}
None => {
@ -2299,6 +2364,7 @@ fn to_trecord_report<'a>(
filename,
doc,
title: "UNFINISHED RECORD TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -2345,6 +2411,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "UNFINISHED TAG UNION TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
Next::Other(Some(c)) if c.is_alphabetic() => {
@ -2366,6 +2433,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "WEIRD TAG NAME".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -2386,6 +2454,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "UNFINISHED TAG UNION TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
},
@ -2411,6 +2480,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "WEIRD TAG NAME".to_string(),
severity: Severity::RuntimeError,
}
}
Next::Other(Some('@')) => {
@ -2427,6 +2497,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "WEIRD TAG NAME".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -2446,6 +2517,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "UNFINISHED TAG UNION TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -2472,6 +2544,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "UNFINISHED TAG UNION TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2496,6 +2569,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "NEED MORE INDENTATION".to_string(),
severity: Severity::RuntimeError,
}
}
None => {
@ -2520,6 +2594,7 @@ fn to_ttag_union_report<'a>(
filename,
doc,
title: "UNFINISHED TAG UNION TYPE".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -2560,6 +2635,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
Next::Other(Some(c)) if c.is_alphabetic() => {
@ -2581,6 +2657,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "WEIRD TAG NAME".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -2603,6 +2680,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -2630,6 +2708,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "WEIRD TAG NAME".to_string(),
severity: Severity::RuntimeError,
}
}
_ => {
@ -2649,6 +2728,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -2676,6 +2756,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2700,6 +2781,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "NEED MORE INDENTATION".to_string(),
severity: Severity::RuntimeError,
}
}
None => {
@ -2724,6 +2806,7 @@ fn to_tinparens_report<'a>(
filename,
doc,
title: "UNFINISHED PARENTHESES".to_string(),
severity: Severity::RuntimeError,
}
}
}
@ -2756,6 +2839,7 @@ fn to_tapply_report<'a>(
filename,
doc,
title: "DOUBLE DOT".to_string(),
severity: Severity::RuntimeError,
}
}
TApply::TrailingDot(row, col) => {
@ -2777,6 +2861,7 @@ fn to_tapply_report<'a>(
filename,
doc,
title: "TRAILING DOT".to_string(),
severity: Severity::RuntimeError,
}
}
TApply::StartIsNumber(row, col) => {
@ -2798,6 +2883,7 @@ fn to_tapply_report<'a>(
filename,
doc,
title: "WEIRD QUALIFIED NAME".to_string(),
severity: Severity::RuntimeError,
}
}
TApply::StartNotUppercase(row, col) => {
@ -2819,6 +2905,7 @@ fn to_tapply_report<'a>(
filename,
doc,
title: "WEIRD QUALIFIED NAME".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2836,6 +2923,7 @@ fn to_tapply_report<'a>(
filename,
doc,
title: "END OF FILE".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2891,6 +2979,7 @@ fn to_header_report<'a>(
filename,
doc,
title: "INCOMPLETE HEADER".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2916,6 +3005,7 @@ fn to_header_report<'a>(
filename,
doc,
title: "MISSING HEADER".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2939,6 +3029,7 @@ fn to_header_report<'a>(
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2962,6 +3053,7 @@ fn to_header_report<'a>(
filename,
doc,
title: "WEIRD APP NAME".to_string(),
severity: Severity::RuntimeError,
}
}
@ -2983,6 +3075,7 @@ fn to_header_report<'a>(
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3020,6 +3113,7 @@ fn to_provides_report<'a>(
filename,
doc,
title: "WEIRD PROVIDES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3044,6 +3138,7 @@ fn to_provides_report<'a>(
filename,
doc,
title: "WEIRD PROVIDES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3082,6 +3177,7 @@ fn to_exposes_report<'a>(
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3106,6 +3202,7 @@ fn to_exposes_report<'a>(
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3144,6 +3241,7 @@ fn to_imports_report<'a>(
filename,
doc,
title: "WEIRD EXPOSES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3168,6 +3266,7 @@ fn to_imports_report<'a>(
filename,
doc,
title: "WEIRD IMPORTS".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3193,6 +3292,7 @@ fn to_imports_report<'a>(
filename,
doc,
title: "WEIRD MODULE NAME".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3231,6 +3331,7 @@ fn to_requires_report<'a>(
filename,
doc,
title: "MISSING REQUIRES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3257,6 +3358,7 @@ fn to_requires_report<'a>(
filename,
doc,
title: "MISSING REQUIRES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3285,6 +3387,7 @@ fn to_requires_report<'a>(
filename,
doc,
title: "BAD REQUIRES RIGIDS".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3321,6 +3424,7 @@ fn to_packages_report<'a>(
filename,
doc,
title: "MISSING PACKAGES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3359,6 +3463,7 @@ fn to_effects_report<'a>(
filename,
doc,
title: "MISSING PACKAGES".to_string(),
severity: Severity::RuntimeError,
}
}
@ -3391,6 +3496,7 @@ fn to_space_report<'a>(
filename,
doc,
title: "TAB CHARACTER".to_string(),
severity: Severity::RuntimeError,
}
}

View File

@ -7,7 +7,7 @@ use roc_types::pretty_print::Parens;
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt};
use std::path::PathBuf;
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use ven_pretty::DocAllocator;
const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#;
@ -19,6 +19,15 @@ pub fn type_problem<'b>(
) -> Report<'b> {
use solve::TypeError::*;
fn report(title: String, doc: RocDocBuilder<'_>, filename: PathBuf) -> Report<'_> {
Report {
title,
filename,
doc,
severity: Severity::RuntimeError,
}
}
match problem {
BadExpr(region, category, found, expected) => {
to_expr_report(alloc, filename, region, category, found, expected)
@ -39,11 +48,7 @@ pub fn type_problem<'b>(
.append(alloc.symbol_unqualified(symbol))])
.append(alloc.reflow("."));
Report {
title,
filename,
doc,
}
report(title, doc, filename)
}
BadType(type_problem) => {
use roc_types::types::Problem::*;
@ -84,20 +89,12 @@ pub fn type_problem<'b>(
"TOO FEW TYPE ARGUMENTS".to_string()
};
Report {
title,
filename,
doc,
}
report(title, doc, filename)
}
CyclicAlias(symbol, region, others) => {
let (doc, title) = cyclic_alias(alloc, symbol, region, others);
Report {
title,
filename,
doc,
}
report(title, doc, filename)
}
other => panic!("unhandled bad type: {:?}", other),
@ -186,6 +183,7 @@ fn report_mismatch<'b>(
title: "TYPE MISMATCH".to_string(),
filename,
doc: alloc.stack(lines),
severity: Severity::RuntimeError,
}
}
@ -223,6 +221,7 @@ fn report_bad_type<'b>(
title: "TYPE MISMATCH".to_string(),
filename,
doc: alloc.stack(lines),
severity: Severity::RuntimeError,
}
}
@ -265,6 +264,7 @@ fn to_expr_report<'b>(
alloc.region(expr_region),
comparison,
]),
severity: Severity::RuntimeError,
}
}
Expected::FromAnnotation(name, _arity, annotation_source, expected_type) => {
@ -351,6 +351,7 @@ fn to_expr_report<'b>(
},
comparison,
]),
severity: Severity::RuntimeError,
}
}
Expected::ForReason(reason, expected_type, region) => match reason {
@ -682,6 +683,7 @@ fn to_expr_report<'b>(
filename,
title: "TYPE MISMATCH".to_string(),
doc,
severity: Severity::RuntimeError,
}
}
}
@ -730,6 +732,7 @@ fn to_expr_report<'b>(
filename,
title: "TOO MANY ARGS".to_string(),
doc: alloc.stack(lines),
severity: Severity::RuntimeError,
}
}
n => {
@ -764,6 +767,7 @@ fn to_expr_report<'b>(
filename,
title: "TOO MANY ARGS".to_string(),
doc: alloc.stack(lines),
severity: Severity::RuntimeError,
}
} else {
let lines = vec![
@ -790,6 +794,7 @@ fn to_expr_report<'b>(
filename,
title: "TOO FEW ARGS".to_string(),
doc: alloc.stack(lines),
severity: Severity::RuntimeError,
}
}
}
@ -1060,6 +1065,7 @@ fn to_pattern_report<'b>(
filename,
title: "TYPE MISMATCH".to_string(),
doc,
severity: Severity::RuntimeError,
}
}
@ -1102,6 +1108,7 @@ fn to_pattern_report<'b>(
filename,
title: "TYPE MISMATCH".to_string(),
doc,
severity: Severity::RuntimeError,
}
}
PReason::WhenMatch { index } => {
@ -1136,6 +1143,7 @@ fn to_pattern_report<'b>(
filename,
title: "TYPE MISMATCH".to_string(),
doc,
severity: Severity::RuntimeError,
}
} else {
let doc = alloc.stack(vec![
@ -1165,6 +1173,7 @@ fn to_pattern_report<'b>(
filename,
title: "TYPE MISMATCH".to_string(),
doc,
severity: Severity::RuntimeError,
}
}
}
@ -1252,6 +1261,7 @@ fn to_circular_report<'b>(
]),
])
},
severity: Severity::RuntimeError,
}
}

View File

@ -57,11 +57,24 @@ pub fn cycle<'b>(
.annotate(Annotation::TypeBlock)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Severity {
/// This will cause a runtime error if some code get srun
/// (e.g. type mismatch, naming error)
RuntimeError,
/// This will never cause the code to misbehave,
/// but should be cleaned up
/// (e.g. unused def, unused import)
Warning,
}
/// A textual report.
pub struct Report<'b> {
pub title: String,
pub filename: PathBuf,
pub doc: RocDocBuilder<'b>,
pub severity: Severity,
}
impl<'b> Report<'b> {
@ -106,6 +119,10 @@ impl<'b> Report<'b> {
])
}
}
pub fn horizontal_rule(palette: &'b Palette) -> String {
format!("{}{}", palette.header, "".repeat(80))
}
}
pub struct Palette<'a> {

View File

@ -16,9 +16,9 @@ mod test_reporting {
use roc_mono::ir::{Procs, Stmt};
use roc_mono::layout::LayoutCache;
use roc_reporting::report::{
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
WHITE_CODE, YELLOW_CODE,
can_problem, mono_problem, parse_problem, type_problem, Report, Severity, BLUE_CODE,
BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE,
UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE,
};
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
use roc_solve::solve;
@ -38,6 +38,7 @@ mod test_reporting {
title: "".to_string(),
doc,
filename: filename_from_string(r"\code\proj\Main.roc"),
severity: Severity::RuntimeError,
}
}
@ -301,7 +302,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
NOT EXPOSED
The List module does not expose a foobar value:
@ -325,7 +326,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNUSED DEFINITION
`y` is not used anywhere in your code.
@ -354,7 +355,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE NAME
The `i` name is first defined here:
@ -391,7 +392,7 @@ mod test_reporting {
// Booly is called a "variable"
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE NAME
The `Booly` name is first defined here:
@ -406,7 +407,7 @@ mod test_reporting {
Since these variables have the same name, it's easy to use the wrong
one on accident. Give one of them a new name.
SYNTAX PROBLEM
UNUSED DEFINITION
`Booly` is not used anywhere in your code.
@ -514,7 +515,7 @@ mod test_reporting {
}
#[test]
fn unused_undefined_argument() {
fn unrecognized_name() {
report_problem_as(
indoc!(
r#"
@ -533,7 +534,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNRECOGNIZED NAME
I cannot find a `bar` value
@ -582,57 +583,79 @@ mod test_reporting {
)
}
// #[test]
// fn report_unused_argument() {
// report_problem_as(
// indoc!(r#"
// y = 9
//
// box = \class, htmlChildren ->
// div [ class ] []
//
// div = 4
//
// box "wizard" []
// "#),
// indoc!(
// r#"
// box doesn't use htmlChildren.
//
// 3│ box = \class, htmlChildren ->
//
// If you don't need htmlChildren, then you can just remove it. However, if you really do need htmlChildren as an argument of box, prefix it with an underscore, like this: "_htmlChildren". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."#
// ),
// );
// }
#[test]
fn unused_arg_and_unused_def() {
report_problem_as(
indoc!(
r#"
y = 9
box = \class, htmlChildren ->
div [ class ] []
div = \_, _ -> 4
box "wizard" []
"#
),
indoc!(
r#"
UNUSED ARGUMENT
`box` doesn't use `htmlChildren`.
3 box = \class, htmlChildren ->
^^^^^^^^^^^^
If you don't need `htmlChildren`, then you can just remove it. However,
if you really do need `htmlChildren` as an argument of `box`, prefix it
with an underscore, like this: "_`htmlChildren`". Adding an underscore
at the start of a variable name is a way of saying that the variable
is not used.
UNUSED DEFINITION
`y` is not used anywhere in your code.
1 y = 9
^
If you didn't intend on using `y` then remove it so future readers of
your code don't wonder why it is there.
"#
),
);
}
// #[test]
// fn report_unused_import() {
// report_problem_as(
// indoc!(r#"
// interface Report
// exposes [
// plainText,
// emText
// ]
// imports [
// Symbol.{ Interns }
// ]
//
// plainText = \str -> PlainText str
//
// emText = \str -> EmText str
// "#),
// indoc!(
// r#"
// Nothing from Symbol is used in this module.
//
// 6│ imports [
// 7│ Symbol.{ Interns }
// ^^^^^^
// 8│ ]
//
// Since Symbol isn't used, you don't need to import it."#
// interface Report
// exposes [
// plainText,
// emText
// ]
// imports [
// Symbol.{ Interns }
// ]
// plainText = \str -> PlainText str
// emText = \str -> EmText str
// "#
// ),
// indoc!(
// r#"
// Nothing from Symbol is used in this module.
// 6│ imports [
// 7│ Symbol.{ Interns }
// ^^^^^^
// 8│ ]
// Since Symbol isn't used, you don't need to import it."#
// ),
// );
// }
@ -709,7 +732,7 @@ mod test_reporting {
),
indoc!(
r#"
<cyan> SYNTAX PROBLEM <reset>
<cyan> UNRECOGNIZED NAME <reset>
I cannot find a `theAdmin` value
@ -727,51 +750,51 @@ mod test_reporting {
);
}
// #[test]
// fn shadowing_type_alias() {
// report_problem_as(
// indoc!(
// r#"
// foo : I64 as I64
// foo = 42
//
// foo
// "#
// ),
// indoc!(
// r#"
// You cannot mix (!=) and (==) without parentheses
//
// 3│ if selectedId != thisId == adminsId then
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// "#
// ),
// )
// }
// #[test]
// fn shadowing_type_alias() {
// report_problem_as(
// indoc!(
// r#"
// foo : I64 as I64
// foo = 42
// #[test]
// fn invalid_as_type_alias() {
// report_problem_as(
// indoc!(
// r#"
// foo : I64 as a
// foo = 42
//
// foo
// "#
// ),
// indoc!(
// r#"
// You cannot mix (!=) and (==) without parentheses
//
// 3│ if selectedId != thisId == adminsId then
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// "#
// ),
// )
// }
// foo
// "#
// ),
// indoc!(
// r#"
// You cannot mix (!=) and (==) without parentheses
// 3│ if selectedId != thisId == adminsId then
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// "#
// ),
// )
// }
// #[test]
// fn invalid_as_type_alias() {
// report_problem_as(
// indoc!(
// r#"
// foo : I64 as a
// foo = 42
// foo
// "#
// ),
// indoc!(
// r#"
// You cannot mix (!=) and (==) without parentheses
// 3│ if selectedId != thisId == adminsId then
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// "#
// ),
// )
// }
#[test]
fn if_condition_not_bool() {
@ -1461,7 +1484,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNRECOGNIZED NAME
I cannot find a `foo` value
@ -1917,7 +1940,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNRECOGNIZED NAME
I cannot find a `ok` value
@ -1952,7 +1975,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNUSED DEFINITION
`ok` is not used anywhere in your code.
@ -1998,7 +2021,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
CIRCULAR DEFINITION
The `f` value is defined directly in terms of itself, causing an
infinite loop.
@ -2022,7 +2045,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
CIRCULAR DEFINITION
The `foo` definition is causing a very tricky infinite loop:
@ -2789,7 +2812,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE FIELD NAME
This record defines the `.x` field twice!
@ -2817,7 +2840,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE FIELD NAME
This record defines the `.x` field twice!
@ -2849,7 +2872,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE FIELD NAME
This record defines the `.x` field twice!
@ -2888,7 +2911,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE FIELD NAME
This record defines the `.x` field twice!
@ -2925,7 +2948,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE FIELD NAME
This record type defines the `.foo` field twice!
@ -2957,7 +2980,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
DUPLICATE TAG NAME
This tag union type defines the `Foo` tag twice!
@ -2990,7 +3013,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
NAMING PROBLEM
This annotation does not match the definition immediately following
it:
@ -3041,7 +3064,7 @@ mod test_reporting {
Only type variables like `a` or `value` can occur in this position.
SYNTAX PROBLEM
UNUSED DEFINITION
`MyAlias` is not used anywhere in your code.
@ -3177,17 +3200,17 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNUSED TYPE ALIAS PARAMETER
The `a` type variable is not used in the `Foo` alias definition:
The `a` type parameter is not used in the `Foo` alias definition:
1 Foo a : [ Foo ]
^
Roc does not allow unused type parameters!
Roc does not allow unused type alias parameters!
Tip: If you want an unused type parameter (a so-called "phantom
type"), read the guide section on phantom data.
type"), read the guide section on phantom values.
"#
),
)
@ -3604,7 +3627,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
MODULE NOT IMPORTED
The `Foo` module is not imported:
@ -4035,7 +4058,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNUSED ARGUMENT
`f` doesn't use `foo`.
@ -5446,7 +5469,7 @@ mod test_reporting {
r#""abc\u(110000)def""#,
indoc!(
r#"
SYNTAX PROBLEM
INVALID UNICODE
This unicode code point is invalid:
@ -5570,7 +5593,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
NOT EXPOSED
The Num module does not expose a if value:
@ -5710,7 +5733,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
NAMING PROBLEM
I am trying to parse an identifier here:
@ -5767,7 +5790,7 @@ mod test_reporting {
),
indoc!(
r#"
SYNTAX PROBLEM
UNRECOGNIZED NAME
I cannot find a `bar` value

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod gen_num {
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 roc_std::{RocDec, RocOrder};
@ -752,7 +752,7 @@ mod gen_num {
y : Dec
y = 2.4
z : Dec
z = 3

View File

@ -891,6 +891,7 @@ fn overflow_frees_list() {
n : I64
n = 9_223_372_036_854_775_807 + (Num.intCast (List.len myList))
index : Nat
index = Num.intCast n
List.get myList index
@ -1018,7 +1019,7 @@ fn specialize_closure() {
y = [1]
f = \{} -> x
g = \{} -> x + List.len y
g = \{} -> x + Num.intCast (List.len y)
[ f, g ]
@ -2292,8 +2293,8 @@ fn build_then_apply_closure() {
(\_ -> x) {}
"#
),
"long string that is malloced",
&'static str
RocStr::from_slice(b"long string that is malloced"),
RocStr
);
}
@ -2398,7 +2399,7 @@ fn call_invalid_layout() {
}
#[test]
#[should_panic(expected = "assert failed!")]
#[should_panic(expected = "An expectation failed!")]
fn expect_fail() {
assert_evals_to!(
indoc!(

View File

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

View File

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

View File

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

View File

@ -3,33 +3,7 @@
use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc;
use roc_std::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
}
use roc_std::{RocList, RocStr};
#[test]
fn str_split_bigger_delimiter_small_str() {
@ -78,8 +52,8 @@ fn str_split_str_concat_repeated() {
"#
),
"JJJJJJJJJJJJJJJJJJJJJJJJJ",
&'static str
RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"),
RocStr
);
}
@ -96,8 +70,8 @@ fn str_split_small_str_bigger_delimiter() {
_ -> ""
"#
),
small_str("JJJ"),
[u8; ROC_STR_MEM_SIZE]
RocStr::from_slice(b"JJJ"),
RocStr
);
}
@ -109,8 +83,11 @@ fn str_split_big_str_small_delimiter() {
Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
"#
),
&["01234567789abcdefghi", "01234567789abcdefghi"],
&'static [&'static str]
RocList::from_slice(&[
RocStr::from_slice(b"01234567789abcdefghi"),
RocStr::from_slice(b"01234567789abcdefghi")
]),
RocList<RocStr>
);
assert_evals_to!(
@ -119,8 +96,11 @@ fn str_split_big_str_small_delimiter() {
Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
"#
),
&["01234567789abcdefghi ", " 01234567789abcdefghi"],
&'static [&'static str]
RocList::from_slice(&[
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" "!"
"#
),
&[small_str("J"), small_str("J"), small_str("J")],
&'static [[u8; ROC_STR_MEM_SIZE]]
RocList::from_slice(&[
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"
"#
),
&["string to split is shorter"],
&'static [&'static str]
RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]),
RocList<RocStr>
);
}
@ -160,9 +144,9 @@ fn str_split_empty_strs() {
Str.split "" ""
"#
),
&[small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
)
RocList::from_slice(&[RocStr::from_slice(b"")]),
RocList<RocStr>
);
}
#[test]
@ -173,8 +157,8 @@ fn str_split_minimal_example() {
Str.split "a," ","
"#
),
&[small_str("a"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]),
RocList<RocStr>
)
}
@ -201,8 +185,12 @@ fn str_split_small_str_big_delimiter() {
"---- ---- ---- ---- ----"
"#
),
&[small_str("1"), small_str("2"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
RocList::from_slice(&[
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("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
RocList::from_slice(&[
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."
"#
),
"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::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."),
RocStr
);
}
#[test]
fn small_str_literal() {
assert_evals_to!(
assert_llvm_evals_to!(
"\"JJJJJJJJJJJJJJJ\"",
[
0x4a,
@ -267,7 +259,7 @@ fn small_str_zeroed_literal() {
// Verifies that we zero out unused bytes in the string.
// This is important so that string equality tests don't randomly
// fail due to unused memory being there!
assert_evals_to!(
assert_llvm_evals_to!(
"\"J\"",
[
0x4a,
@ -293,7 +285,7 @@ fn small_str_zeroed_literal() {
#[test]
fn small_str_concat_empty_first_arg() {
assert_evals_to!(
assert_llvm_evals_to!(
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
[
0x4a,
@ -319,7 +311,7 @@ fn small_str_concat_empty_first_arg() {
#[test]
fn small_str_concat_empty_second_arg() {
assert_evals_to!(
assert_llvm_evals_to!(
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
[
0x4a,
@ -347,14 +339,14 @@ fn small_str_concat_empty_second_arg() {
fn small_str_concat_small_to_big() {
assert_evals_to!(
r#"Str.concat "abc" " this is longer than 15 chars""#,
"abc this is longer than 15 chars",
&'static str
RocStr::from_slice(b"abc this is longer than 15 chars"),
RocStr
);
}
#[test]
fn small_str_concat_small_to_small_staying_small() {
assert_evals_to!(
assert_llvm_evals_to!(
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
[
0x4a,
@ -382,14 +374,14 @@ fn small_str_concat_small_to_small_staying_small() {
fn small_str_concat_small_to_small_overflow_to_big() {
assert_evals_to!(
r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
"abcdefghijklmnopqrstuvwxyz",
&'static str
RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"),
RocStr
);
}
#[test]
fn str_concat_empty() {
assert_evals_to!(r#"Str.concat "" """#, "", &'static str);
assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
}
#[test]
@ -511,10 +503,18 @@ fn str_from_int() {
);
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);
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]
@ -785,8 +785,8 @@ fn nested_recursive_literal() {
printExpr expr
"#
),
"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))",
&'static str
RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"),
RocStr
);
}
@ -820,14 +820,18 @@ fn str_from_float() {
#[test]
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!(
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,
105, 110, 103
],
&[u8]
]),
RocList<u8>
);
}

View File

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

View File

@ -1,3 +1,4 @@
use inkwell::module::Module;
use libloading::Library;
use roc_build::link::module_to_dylib;
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_module::symbol::Symbol;
use roc_mono::ir::OptLevel;
use roc_std::{RocDec, RocList, RocOrder, RocStr};
use roc_types::subs::VarStore;
use target_lexicon::Triple;
fn promote_expr_to_module(src: &str) -> String {
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
// so "normally" it is dead, only at testing time is it used
#[allow(dead_code)]
#[inline(never)]
pub fn helper<'a>(
#[allow(clippy::too_many_arguments)]
fn create_llvm_module<'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) {
target: &Triple,
opt_level: OptLevel,
) -> (&'static str, String, &'a Module<'a>) {
use std::path::{Path, PathBuf};
let filename = PathBuf::from("Test.roc");
@ -53,7 +57,6 @@ pub fn helper<'a>(
module_src = &temp;
}
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let exposed_types = MutMap::default();
@ -169,13 +172,7 @@ pub fn helper<'a>(
}
let builder = context.create_builder();
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 = roc_gen_llvm::llvm::build::module_from_builtins(target, context, "app");
let module = arena.alloc(module);
let (module_pass, function_pass) =
@ -255,10 +252,318 @@ pub fn helper<'a>(
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
let lib = module_to_dylib(env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors.join("\n"), env.module)
}
(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]
@ -288,41 +593,282 @@ macro_rules! assert_llvm_evals_to {
let expected = $expected;
#[allow(clippy::redundant_closure_call)]
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)
};
($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) => {
assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
$crate::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
};
}
#[macro_export]
macro_rules! assert_evals_to {
($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) => {
// 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_rules! assert_non_opt_evals_to {
($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) => {
// 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) => {{
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

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

View File

@ -34,10 +34,17 @@ extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment;
const DEBUG: bool = false;
return malloc(size);
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
if (DEBUG) {
var ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d})\n", .{ ptr, alignment }) catch unreachable;
return ptr;
} else {
return malloc(size);
}
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
@ -48,7 +55,10 @@ export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
_ = alignment;
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
}
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
}
@ -155,26 +165,50 @@ export fn roc_fx_putLine(rocPath: str.RocStr) callconv(.C) void {
const GetInt = extern struct {
value: i64,
error_code: u8,
error_code: bool,
is_error: bool,
};
pub export fn roc_fx_getInt() GetInt {
comptime {
if (@sizeOf(usize) == 8) {
@export(roc_fx_getInt_64bit, .{ .name = "roc_fx_getInt" });
} else {
@export(roc_fx_getInt_32bit, .{ .name = "roc_fx_getInt" });
}
}
fn roc_fx_getInt_64bit() callconv(.C) GetInt {
if (roc_fx_getInt_help()) |value| {
const get_int = GetInt{ .is_error = false, .value = value, .error_code = 0 };
const get_int = GetInt{ .is_error = false, .value = value, .error_code = false };
return get_int;
} else |err| switch (err) {
error.InvalidCharacter => {
return GetInt{ .is_error = true, .value = 0, .error_code = 0 };
return GetInt{ .is_error = true, .value = 0, .error_code = false };
},
else => {
return GetInt{ .is_error = true, .value = 0, .error_code = 1 };
return GetInt{ .is_error = true, .value = 0, .error_code = true };
},
}
return 0;
}
fn roc_fx_getInt_32bit(output: *GetInt) callconv(.C) void {
if (roc_fx_getInt_help()) |value| {
const get_int = GetInt{ .is_error = false, .value = value, .error_code = false };
output.* = get_int;
} else |err| switch (err) {
error.InvalidCharacter => {
output.* = GetInt{ .is_error = true, .value = 0, .error_code = false };
},
else => {
output.* = GetInt{ .is_error = true, .value = 0, .error_code = true };
},
}
return;
}
fn roc_fx_getInt_help() !i64 {
const stdin = std.io.getStdIn().reader();
var buf: [40]u8 = undefined;

View File

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

View File

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

View File

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

View File

@ -19,11 +19,11 @@ bench = false
[dependencies]
roc_collections = { path = "../compiler/collections" }
bumpalo = { version = "3.6.1", features = ["collections"] }
bumpalo = { version = "^3.6", features = ["collections"] }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
iced-x86 = "1.14.0"
memmap2 = "0.3"
object = { version = "0.26", features = ["read"] }
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3"
iced-x86 = "^1.14"
memmap2 = "^0.3"
object = { version = "^0.26", features = ["read"] }
serde = { version = "^1.0", features = ["derive"] }
bincode = "^1.3"

View File

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

View File

@ -44,6 +44,12 @@ pub struct RocList<T> {
length: usize,
}
impl<T: Clone> Clone for RocList<T> {
fn clone(&self) -> Self {
Self::from_slice(self.as_slice())
}
}
#[derive(Clone, Copy, Debug)]
pub enum Storage {
ReadOnly,
@ -614,7 +620,9 @@ impl RocStr {
}
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()) }
} else {
unsafe { core::slice::from_raw_parts(self.elements, self.length) }
@ -718,7 +726,7 @@ impl Clone for RocStr {
impl Drop for RocStr {
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();
unsafe {

View File

@ -17,6 +17,7 @@ let
linuxInputs = with pkgs;
lib.optionals stdenv.isLinux [
glibc_multi
valgrind
vulkan-headers
vulkan-loader
@ -49,7 +50,6 @@ let
zig
# lib deps
glibc_multi
libffi
libxml2
ncurses
@ -70,13 +70,9 @@ in pkgs.mkShell {
# Additional Env vars
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;
lib.makeLibraryPath ([
pkg-config
stdenv.cc.cc.lib
libffi
ncurses
zlib
] ++ linuxInputs);
lib.makeLibraryPath
([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] ++ linuxInputs);
}