mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 21:39:07 +00:00
merge upstream/main
This commit is contained in:
commit
cec67721e6
59 changed files with 2542 additions and 990 deletions
|
@ -3,7 +3,7 @@ use roc_error_macros::internal_error;
|
|||
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
||||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::{EntryPoint, LoadedModule, MonomorphizedModule};
|
||||
use roc_load::{EntryPoint, ExpectMetadata, LoadedModule, MonomorphizedModule};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
|
@ -174,47 +174,60 @@ impl Deref for CodeObject {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CodeGenBackend {
|
||||
Assembly,
|
||||
Llvm,
|
||||
Wasm,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CodeGenOptions {
|
||||
pub backend: CodeGenBackend,
|
||||
pub opt_level: OptLevel,
|
||||
pub emit_debug_info: bool,
|
||||
}
|
||||
|
||||
type GenFromMono<'a> = (CodeObject, CodeGenTiming, ExpectMetadata<'a>);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn gen_from_mono_module(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
pub fn gen_from_mono_module<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
code_gen_options: CodeGenOptions,
|
||||
preprocessed_host_path: &Path,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
match opt_level {
|
||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => gen_from_mono_module_llvm(
|
||||
arena,
|
||||
loaded,
|
||||
roc_file_path,
|
||||
target,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
),
|
||||
OptLevel::Development => gen_from_mono_module_dev(
|
||||
) -> GenFromMono<'a> {
|
||||
match code_gen_options.backend {
|
||||
CodeGenBackend::Assembly => gen_from_mono_module_dev(
|
||||
arena,
|
||||
loaded,
|
||||
target,
|
||||
preprocessed_host_path,
|
||||
wasm_dev_stack_bytes,
|
||||
),
|
||||
CodeGenBackend::Llvm => {
|
||||
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
|
||||
}
|
||||
CodeGenBackend::Wasm => {
|
||||
// emit wasm via the llvm backend
|
||||
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO how should imported modules factor into this? What if those use builtins too?
|
||||
// TODO this should probably use more helper functions
|
||||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||
fn gen_from_mono_module_llvm(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_llvm<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
code_gen_options: CodeGenOptions,
|
||||
) -> GenFromMono<'a> {
|
||||
use crate::target::{self, convert_opt_level};
|
||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
use inkwell::context::Context;
|
||||
|
@ -263,6 +276,12 @@ fn gen_from_mono_module_llvm(
|
|||
}
|
||||
}
|
||||
|
||||
let CodeGenOptions {
|
||||
backend: _,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
} = code_gen_options;
|
||||
|
||||
let builder = context.create_builder();
|
||||
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
|
||||
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
@ -278,7 +297,11 @@ fn gen_from_mono_module_llvm(
|
|||
interns: loaded.interns,
|
||||
module,
|
||||
target_info,
|
||||
mode: LlvmBackendMode::Binary,
|
||||
mode: match opt_level {
|
||||
OptLevel::Development => LlvmBackendMode::BinaryDev,
|
||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
|
||||
},
|
||||
|
||||
exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(),
|
||||
};
|
||||
|
||||
|
@ -430,17 +453,22 @@ fn gen_from_mono_module_llvm(
|
|||
(
|
||||
CodeObject::MemoryBuffer(memory_buffer),
|
||||
CodeGenTiming { code_gen },
|
||||
ExpectMetadata {
|
||||
interns: env.interns,
|
||||
layout_interner: loaded.layout_interner,
|
||||
expectations: loaded.expectations,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
fn gen_from_mono_module_dev(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_dev<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
target: &target_lexicon::Triple,
|
||||
preprocessed_host_path: &Path,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
use target_lexicon::Architecture;
|
||||
|
||||
match target.architecture {
|
||||
|
@ -458,13 +486,13 @@ fn gen_from_mono_module_dev(
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "target-wasm32"))]
|
||||
pub fn gen_from_mono_module_dev(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
pub fn gen_from_mono_module_dev<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
target: &target_lexicon::Triple,
|
||||
_host_input_path: &Path,
|
||||
_wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
use target_lexicon::Architecture;
|
||||
|
||||
match target.architecture {
|
||||
|
@ -476,12 +504,12 @@ pub fn gen_from_mono_module_dev(
|
|||
}
|
||||
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
fn gen_from_mono_module_dev_wasm32(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_dev_wasm32<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
preprocessed_host_path: &Path,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
let code_gen_start = Instant::now();
|
||||
let MonomorphizedModule {
|
||||
module_id,
|
||||
|
@ -530,14 +558,19 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
(
|
||||
CodeObject::Vector(final_binary_bytes),
|
||||
CodeGenTiming { code_gen },
|
||||
ExpectMetadata {
|
||||
interns,
|
||||
layout_interner,
|
||||
expectations: loaded.expectations,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn gen_from_mono_module_dev_assembly(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_dev_assembly<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
target: &target_lexicon::Triple,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
let code_gen_start = Instant::now();
|
||||
|
||||
let lazy_literals = true;
|
||||
|
@ -569,5 +602,13 @@ fn gen_from_mono_module_dev_assembly(
|
|||
.write()
|
||||
.expect("failed to build output object");
|
||||
|
||||
(CodeObject::Vector(module_out), CodeGenTiming { code_gen })
|
||||
(
|
||||
CodeObject::Vector(module_out),
|
||||
CodeGenTiming { code_gen },
|
||||
ExpectMetadata {
|
||||
interns,
|
||||
layout_interner,
|
||||
expectations: loaded.expectations,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const SIGUSR1: c_int = 10;
|
||||
|
||||
|
@ -18,6 +19,71 @@ pub fn setSharedBuffer(ptr: [*]u8, length: usize) callconv(.C) usize {
|
|||
return 0;
|
||||
}
|
||||
|
||||
pub fn expectFailedStart() callconv(.C) [*]u8 {
|
||||
pub fn expectFailedStartSharedBuffer() callconv(.C) [*]u8 {
|
||||
return SHARED_BUFFER.ptr;
|
||||
}
|
||||
|
||||
pub fn expectFailedStartSharedFile() callconv(.C) [*]u8 {
|
||||
// IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
||||
var name: [100]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(name[0..100], "/roc_expect_buffer_{}\x00", .{roc_getppid()}) catch unreachable;
|
||||
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
const shared_fd = roc_shm_open(@ptrCast(*const i8, &name), O_RDWR | O_CREAT, 0o666);
|
||||
|
||||
const length = 4096;
|
||||
|
||||
const shared_ptr = roc_mmap(
|
||||
null,
|
||||
length,
|
||||
PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
shared_fd,
|
||||
0,
|
||||
);
|
||||
|
||||
const ptr = @ptrCast([*]u8, shared_ptr);
|
||||
|
||||
return ptr;
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
extern fn roc_send_signal(pid: c_int, sig: c_int) c_int;
|
||||
extern fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn roc_getppid() c_int;
|
||||
|
||||
pub fn readSharedBufferEnv() callconv(.C) void {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
|
||||
// IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
||||
var name: [100]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(name[0..100], "/roc_expect_buffer_{}\x00", .{roc_getppid()}) catch unreachable;
|
||||
|
||||
const shared_fd = roc_shm_open(@ptrCast(*const i8, &name), O_RDWR | O_CREAT, 0o666);
|
||||
const length = 4096;
|
||||
|
||||
const shared_ptr = roc_mmap(
|
||||
null,
|
||||
length,
|
||||
PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
shared_fd,
|
||||
0,
|
||||
);
|
||||
|
||||
const ptr = @ptrCast([*]u8, shared_ptr);
|
||||
|
||||
SHARED_BUFFER = ptr[0..length];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expectFailedFinalize() callconv(.C) void {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
const parent_pid = roc_getppid();
|
||||
|
||||
_ = roc_send_signal(parent_pid, SIGUSR1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,10 +169,14 @@ comptime {
|
|||
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
|
||||
|
||||
if (builtin.target.cpu.arch != .wasm32) {
|
||||
exportUtilsFn(expect.expectFailedStart, "expect_failed_start");
|
||||
exportUtilsFn(expect.expectFailedStartSharedBuffer, "expect_failed_start_shared_buffer");
|
||||
exportUtilsFn(expect.expectFailedStartSharedFile, "expect_failed_start_shared_file");
|
||||
exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize");
|
||||
|
||||
// sets the buffer used for expect failures
|
||||
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });
|
||||
|
||||
exportUtilsFn(expect.readSharedBufferEnv, "read_env_shared_buffer");
|
||||
}
|
||||
|
||||
if (builtin.target.cpu.arch == .aarch64) {
|
||||
|
|
|
@ -22,6 +22,24 @@ extern fn roc_panic(c_ptr: *const anyopaque, tag_id: u32) callconv(.C) void;
|
|||
// should work just like libc memcpy (we can't assume libc is present)
|
||||
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn testing_roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
fn testing_roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn testing_roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn testing_roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
const builtin = @import("builtin");
|
||||
// During tests, use the testing allocators to satisfy these functions.
|
||||
|
@ -31,6 +49,13 @@ comptime {
|
|||
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
|
||||
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
|
||||
@export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong });
|
||||
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(testing_roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(testing_roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(testing_roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(testing_roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -215,6 +215,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
|||
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||
if error_str.contains("FileNotFound")
|
||||
|| error_str.contains("unable to save cached ZIR code")
|
||||
|| error_str.contains("LLVM failed to emit asm")
|
||||
{
|
||||
if flaky_fail_counter == 10 {
|
||||
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
|
||||
|
|
|
@ -368,30 +368,26 @@ contains = \list, needle ->
|
|||
## You can use it in a pipeline:
|
||||
##
|
||||
## [2, 4, 8]
|
||||
## |> List.walk { start: 0, step: Num.add }
|
||||
## |> List.walk 0 Num.add
|
||||
##
|
||||
## This returns 14 because:
|
||||
## * `state` starts at 0 (because of `start: 0`)
|
||||
## * `state` starts at 0
|
||||
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
|
||||
##
|
||||
## Here is a table of how `state` changes as [List.walk] walks over the elements
|
||||
## `[2, 4, 8]` using #Num.add as its `step` function to determine the next `state`.
|
||||
## `[2, 4, 8]` using [Num.add] as its `step` function to determine the next `state`.
|
||||
##
|
||||
## `state` | `elem` | `step state elem` (`Num.add state elem`)
|
||||
## --------+--------+-----------------------------------------
|
||||
## 0 | |
|
||||
## 0 | 2 | 2
|
||||
## 2 | 4 | 6
|
||||
## 6 | 8 | 14
|
||||
## state | elem | Num.add state elem
|
||||
## :---: | :---: | :----------------:
|
||||
## 0 | |
|
||||
## 0 | 2 | 2
|
||||
## 2 | 4 | 6
|
||||
## 6 | 8 | 14
|
||||
##
|
||||
## So `state` goes through these changes:
|
||||
## 1. `0` (because of `start: 0`)
|
||||
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
|
||||
## The following returns -6:
|
||||
##
|
||||
## [1, 2, 3]
|
||||
## |> List.walk { start: 0, step: Num.sub }
|
||||
##
|
||||
## This returns -6 because
|
||||
## |> List.walk 0 Num.sub
|
||||
##
|
||||
## Note that in other languages, `walk` is sometimes called `reduce`,
|
||||
## `fold`, `foldLeft`, or `foldl`.
|
||||
|
|
|
@ -404,8 +404,12 @@ pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
|
|||
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||
|
||||
pub const UTILS_EXPECT_FAILED_START: &str = "roc_builtins.utils.expect_failed_start";
|
||||
pub const UTILS_EXPECT_FAILED_START_SHARED_BUFFER: &str =
|
||||
"roc_builtins.utils.expect_failed_start_shared_buffer";
|
||||
pub const UTILS_EXPECT_FAILED_START_SHARED_FILE: &str =
|
||||
"roc_builtins.utils.expect_failed_start_shared_file";
|
||||
pub const UTILS_EXPECT_FAILED_FINALIZE: &str = "roc_builtins.utils.expect_failed_finalize";
|
||||
pub const UTILS_EXPECT_READ_ENV_SHARED_BUFFER: &str = "roc_builtins.utils.read_env_shared_buffer";
|
||||
|
||||
pub const UTILS_LONGJMP: &str = "longjmp";
|
||||
pub const UTILS_SETJMP: &str = "setjmp";
|
||||
|
|
|
@ -163,6 +163,7 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
|
|||
pub enum LlvmBackendMode {
|
||||
/// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host
|
||||
Binary,
|
||||
BinaryDev,
|
||||
/// Creates a test wrapper around the main roc function to catch and report panics.
|
||||
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
|
||||
GenTest,
|
||||
|
@ -174,6 +175,7 @@ impl LlvmBackendMode {
|
|||
pub(crate) fn has_host(self) -> bool {
|
||||
match self {
|
||||
LlvmBackendMode::Binary => true,
|
||||
LlvmBackendMode::BinaryDev => true,
|
||||
LlvmBackendMode::GenTest => false,
|
||||
LlvmBackendMode::WasmGenTest => true,
|
||||
LlvmBackendMode::CliTest => false,
|
||||
|
@ -184,6 +186,7 @@ impl LlvmBackendMode {
|
|||
fn returns_roc_result(self) -> bool {
|
||||
match self {
|
||||
LlvmBackendMode::Binary => false,
|
||||
LlvmBackendMode::BinaryDev => false,
|
||||
LlvmBackendMode::GenTest => true,
|
||||
LlvmBackendMode::WasmGenTest => true,
|
||||
LlvmBackendMode::CliTest => true,
|
||||
|
@ -193,6 +196,7 @@ impl LlvmBackendMode {
|
|||
fn runs_expects(self) -> bool {
|
||||
match self {
|
||||
LlvmBackendMode::Binary => false,
|
||||
LlvmBackendMode::BinaryDev => true,
|
||||
LlvmBackendMode::GenTest => false,
|
||||
LlvmBackendMode::WasmGenTest => false,
|
||||
LlvmBackendMode::CliTest => true,
|
||||
|
@ -2824,6 +2828,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
lookups,
|
||||
);
|
||||
|
||||
if let LlvmBackendMode::BinaryDev = env.mode {
|
||||
crate::llvm::expect::finalize(env);
|
||||
}
|
||||
|
||||
bd.build_unconditional_branch(then_block);
|
||||
}
|
||||
roc_target::PtrWidth::Bytes4 => {
|
||||
|
@ -3925,7 +3933,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
|
|||
)
|
||||
}
|
||||
|
||||
LlvmBackendMode::Binary => {}
|
||||
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {}
|
||||
}
|
||||
|
||||
// a generic version that writes the result into a passed *u8 pointer
|
||||
|
@ -3976,7 +3984,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
|
|||
roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
|
||||
}
|
||||
|
||||
LlvmBackendMode::Binary => basic_type_from_layout(env, &return_layout),
|
||||
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
|
||||
basic_type_from_layout(env, &return_layout)
|
||||
}
|
||||
};
|
||||
|
||||
let size: BasicValueEnum = return_type.size_of().unwrap().into();
|
||||
|
@ -4948,7 +4958,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
|||
GenTest | WasmGenTest | CliTest => {
|
||||
/* no host, or exposing types is not supported */
|
||||
}
|
||||
Binary => {
|
||||
Binary | BinaryDev => {
|
||||
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
|
||||
expose_alias_to_host(
|
||||
env,
|
||||
|
|
|
@ -14,7 +14,8 @@ use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
|||
use roc_region::all::Region;
|
||||
|
||||
use super::build::{
|
||||
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, Scope,
|
||||
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, LlvmBackendMode,
|
||||
Scope,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -93,6 +94,16 @@ fn write_state<'a, 'ctx, 'env>(
|
|||
env.builder.build_store(offset_ptr, offset);
|
||||
}
|
||||
|
||||
pub(crate) fn finalize(env: &Env) {
|
||||
let func = env
|
||||
.module
|
||||
.get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE)
|
||||
.unwrap();
|
||||
|
||||
env.builder
|
||||
.build_call(func, &[], "call_expect_failed_finalize");
|
||||
}
|
||||
|
||||
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
|
@ -101,10 +112,13 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
|
|||
region: Region,
|
||||
lookups: &[Symbol],
|
||||
) {
|
||||
let func = env
|
||||
.module
|
||||
.get_function(bitcode::UTILS_EXPECT_FAILED_START)
|
||||
.unwrap();
|
||||
let start_function = if let LlvmBackendMode::BinaryDev = env.mode {
|
||||
bitcode::UTILS_EXPECT_FAILED_START_SHARED_FILE
|
||||
} else {
|
||||
bitcode::UTILS_EXPECT_FAILED_START_SHARED_BUFFER
|
||||
};
|
||||
|
||||
let func = env.module.get_function(start_function).unwrap();
|
||||
|
||||
let call_result = env
|
||||
.builder
|
||||
|
|
|
@ -155,10 +155,32 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
|
|||
}
|
||||
}
|
||||
|
||||
unreachable_function(env, "roc_getppid");
|
||||
unreachable_function(env, "roc_mmap");
|
||||
unreachable_function(env, "roc_send_signal");
|
||||
unreachable_function(env, "roc_shm_open");
|
||||
|
||||
add_sjlj_roc_panic(env)
|
||||
}
|
||||
}
|
||||
|
||||
fn unreachable_function(env: &Env, name: &str) {
|
||||
// The type of this function (but not the implementation) should have
|
||||
// already been defined by the builtins, which rely on it.
|
||||
let fn_val = env.module.get_function(name).unwrap();
|
||||
|
||||
// Add a basic block for the entry point
|
||||
let entry = env.context.append_basic_block(fn_val, "entry");
|
||||
|
||||
env.builder.position_at_end(entry);
|
||||
|
||||
env.builder.build_unreachable();
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
crate::llvm::build::verify_fn(fn_val);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
|
||||
let ctx = env.context;
|
||||
let module = env.module;
|
||||
|
|
|
@ -120,7 +120,11 @@ macro_rules! run_jit_function {
|
|||
|
||||
$transform(success)
|
||||
}
|
||||
Err(error_msg) => panic!("Roc failed with message: {}", error_msg),
|
||||
Err(error_msg) => {
|
||||
eprintln!("This Roc code crashed with: \"{error_msg}\"");
|
||||
|
||||
Expr::MalformedClosure
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ const SKIP_SUBS_CACHE: bool = {
|
|||
|
||||
pub use roc_load_internal::docs;
|
||||
pub use roc_load_internal::file::{
|
||||
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadResult, LoadStart, LoadedModule,
|
||||
LoadingProblem, MonomorphizedModule, Phase, Threading,
|
||||
EntryPoint, ExecutionMode, ExpectMetadata, Expectations, LoadConfig, LoadResult, LoadStart,
|
||||
LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading,
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
|
@ -705,6 +705,13 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub expectations: VecMap<ModuleId, Expectations>,
|
||||
}
|
||||
|
||||
/// Values used to render expect output
|
||||
pub struct ExpectMetadata<'a> {
|
||||
pub interns: Interns,
|
||||
pub layout_interner: SingleThreadedInterner<'a, Layout<'a>>,
|
||||
pub expectations: VecMap<ModuleId, Expectations>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EntryPoint<'a> {
|
||||
Executable {
|
||||
|
|
|
@ -615,6 +615,14 @@ impl<'a> CommentOrNewline<'a> {
|
|||
DocComment(comment_str) => format!("##{}", comment_str),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn comment_str(&'a self) -> Option<&'a str> {
|
||||
match self {
|
||||
CommentOrNewline::LineComment(s) => Some(*s),
|
||||
CommentOrNewline::DocComment(s) => Some(*s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
|
|
@ -56,13 +56,13 @@ pub struct ExprParseOptions {
|
|||
/// This is usually true, but false within list/record literals
|
||||
/// because the comma separating backpassing arguments conflicts
|
||||
/// with the comma separating literal elements
|
||||
accept_multi_backpassing: bool,
|
||||
pub accept_multi_backpassing: bool,
|
||||
|
||||
/// Check for the `->` token, and raise an error if found
|
||||
/// This is usually true, but false in if-guards
|
||||
///
|
||||
/// > Just foo if foo == 2 -> ...
|
||||
check_for_arrow: bool,
|
||||
pub check_for_arrow: bool,
|
||||
}
|
||||
|
||||
impl Default for ExprParseOptions {
|
||||
|
@ -896,6 +896,65 @@ pub fn parse_single_def<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// This is a macro only because trying to make it be a function caused lifetime issues.
|
||||
#[macro_export]
|
||||
macro_rules! join_ann_to_body {
|
||||
($arena:expr, $loc_pattern:expr, $loc_def_expr:expr, $ann_pattern:expr, $ann_type:expr, $spaces_before_current:expr, $region:expr) => {{
|
||||
// join this body with the preceding annotation
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: $arena.alloc(*$ann_pattern),
|
||||
ann_type: $arena.alloc(*$ann_type),
|
||||
comment: $spaces_before_current
|
||||
.first()
|
||||
.and_then($crate::ast::CommentOrNewline::comment_str),
|
||||
body_pattern: $arena.alloc($loc_pattern),
|
||||
body_expr: *$arena.alloc($loc_def_expr),
|
||||
};
|
||||
|
||||
(
|
||||
value_def,
|
||||
roc_region::all::Region::span_across(&$ann_pattern.region, &$region),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
// This is a macro only because trying to make it be a function caused lifetime issues.
|
||||
#[macro_export]
|
||||
macro_rules! join_alias_to_body {
|
||||
($arena:expr, $loc_pattern:expr, $loc_def_expr:expr, $header:expr, $ann_type:expr, $spaces_before_current:expr, $region:expr) => {{
|
||||
use roc_region::all::Region;
|
||||
|
||||
// This is a case like
|
||||
// UserId x : [UserId Int]
|
||||
// UserId x = UserId 42
|
||||
// We optimistically parsed the first line as an alias; we now turn it
|
||||
// into an annotation.
|
||||
|
||||
let loc_name = $arena.alloc($header.name.map(|x| Pattern::Tag(x)));
|
||||
let ann_pattern = Pattern::Apply(loc_name, $header.vars);
|
||||
|
||||
let vars_region = Region::across_all($header.vars.iter().map(|v| &v.region));
|
||||
let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region);
|
||||
let loc_ann_pattern = Loc::at(region_ann_pattern, ann_pattern);
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: $arena.alloc(loc_ann_pattern),
|
||||
ann_type: $arena.alloc(*$ann_type),
|
||||
comment: $spaces_before_current
|
||||
.first()
|
||||
.and_then($crate::ast::CommentOrNewline::comment_str),
|
||||
body_pattern: $arena.alloc($loc_pattern),
|
||||
body_expr: *$arena.alloc($loc_def_expr),
|
||||
};
|
||||
|
||||
(
|
||||
value_def,
|
||||
Region::span_across(&$header.name.region, &$region),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
fn parse_defs_end<'a>(
|
||||
_options: ExprParseOptions,
|
||||
min_indent: u32,
|
||||
|
@ -920,94 +979,64 @@ fn parse_defs_end<'a>(
|
|||
Either::Second(value_def) => {
|
||||
// If we got a ValueDef::Body, check if a type annotation preceded it.
|
||||
// If so, we may need to combine them into an AnnotatedBody.
|
||||
match value_def {
|
||||
let joined = match value_def {
|
||||
ValueDef::Body(loc_pattern, loc_def_expr)
|
||||
if spaces_before_current.len() <= 1 =>
|
||||
{
|
||||
let region =
|
||||
Region::span_across(&loc_pattern.region, &loc_def_expr.region);
|
||||
|
||||
let comment = match spaces_before_current.get(0) {
|
||||
Some(CommentOrNewline::LineComment(s)) => Some(*s),
|
||||
Some(CommentOrNewline::DocComment(s)) => Some(*s),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match defs.last() {
|
||||
Some(Err(ValueDef::Annotation(ann_pattern, ann_type))) => {
|
||||
// join this body with the preceding annotation
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: arena.alloc(*ann_pattern),
|
||||
ann_type: arena.alloc(*ann_type),
|
||||
comment,
|
||||
body_pattern: arena.alloc(loc_pattern),
|
||||
body_expr: arena.alloc(loc_def_expr),
|
||||
};
|
||||
|
||||
let region =
|
||||
Region::span_across(&ann_pattern.region, ®ion);
|
||||
let (value_def, region) = join_ann_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
spaces_before_current,
|
||||
region
|
||||
);
|
||||
|
||||
defs.replace_with_value_def(
|
||||
defs.tags.len() - 1,
|
||||
value_def,
|
||||
region,
|
||||
)
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Some(Ok(TypeDef::Alias {
|
||||
header,
|
||||
ann: ann_type,
|
||||
})) => {
|
||||
// This is a case like
|
||||
// UserId x : [UserId Int]
|
||||
// UserId x = UserId 42
|
||||
// We optimistically parsed the first line as an alias; we now turn it
|
||||
// into an annotation.
|
||||
|
||||
let loc_name =
|
||||
arena.alloc(header.name.map(|x| Pattern::Tag(x)));
|
||||
let ann_pattern = Pattern::Apply(loc_name, header.vars);
|
||||
|
||||
let vars_region = Region::across_all(
|
||||
header.vars.iter().map(|v| &v.region),
|
||||
let (value_def, region) = join_alias_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
header,
|
||||
ann_type,
|
||||
spaces_before_current,
|
||||
region
|
||||
);
|
||||
let region_ann_pattern =
|
||||
Region::span_across(&loc_name.region, &vars_region);
|
||||
let loc_ann_pattern =
|
||||
Loc::at(region_ann_pattern, ann_pattern);
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: arena.alloc(loc_ann_pattern),
|
||||
ann_type: arena.alloc(*ann_type),
|
||||
comment,
|
||||
body_pattern: arena.alloc(loc_pattern),
|
||||
body_expr: arena.alloc(loc_def_expr),
|
||||
};
|
||||
|
||||
let region =
|
||||
Region::span_across(&header.name.region, ®ion);
|
||||
|
||||
defs.replace_with_value_def(
|
||||
defs.tags.len() - 1,
|
||||
value_def,
|
||||
region,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// the previous and current def can't be joined up
|
||||
defs.push_value_def(
|
||||
value_def,
|
||||
region,
|
||||
spaces_before_current,
|
||||
&[],
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// the previous and current def can't be joined up
|
||||
defs.push_value_def(value_def, region, spaces_before_current, &[]);
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !joined {
|
||||
// the previous and current def can't be joined up
|
||||
defs.push_value_def(value_def, region, spaces_before_current, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1021,9 +1050,9 @@ fn parse_defs_end<'a>(
|
|||
}
|
||||
|
||||
pub struct SingleDef<'a> {
|
||||
type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
|
||||
region: Region,
|
||||
spaces_before: &'a [CommentOrNewline<'a>],
|
||||
pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
|
||||
pub region: Region,
|
||||
pub spaces_before: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
fn parse_defs_expr<'a>(
|
||||
|
|
|
@ -190,6 +190,150 @@ pub enum Problem {
|
|||
},
|
||||
}
|
||||
|
||||
impl Problem {
|
||||
/// Returns a Region value from the Problem, if possible.
|
||||
/// Some problems have more than one region; in those cases,
|
||||
/// this tries to pick the one that's closest to the original
|
||||
/// definition site, since that's what the REPL uses this for:
|
||||
/// filtering out errors and warnings from wrapped defs based
|
||||
/// on their Region being outside the expression currently being evaluated.
|
||||
pub fn region(&self) -> Option<Region> {
|
||||
match self {
|
||||
Problem::UnusedDef(_, region)
|
||||
| Problem::Shadowing {
|
||||
original_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::UnusedImport(_, region)
|
||||
| Problem::UnusedModuleImport(_, region)
|
||||
| Problem::UnknownGeneratesWith(Loc { region, .. })
|
||||
| Problem::UnusedArgument(_, _, _, region)
|
||||
| Problem::UnusedBranchDef(_, region)
|
||||
| Problem::PrecedenceProblem(PrecedenceProblem::BothNonAssociative(region, _, _))
|
||||
| Problem::UnsupportedPattern(_, region)
|
||||
| Problem::CyclicAlias(_, region, _, _)
|
||||
| Problem::PhantomTypeArgument {
|
||||
variable_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::UnboundTypeVariable {
|
||||
one_occurrence: region,
|
||||
..
|
||||
}
|
||||
| Problem::DuplicateRecordFieldValue {
|
||||
record_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::DuplicateRecordFieldType {
|
||||
record_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::InvalidOptionalValue {
|
||||
record_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::DuplicateTag {
|
||||
tag_union_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region: region,
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::InvalidOptionalValue {
|
||||
record_region: region,
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::UnsupportedPattern(region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedPattern(_, region))
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope(Loc { region, .. }, _))
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueNotDefined {
|
||||
usage: Loc { region, .. },
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueOutsideScope {
|
||||
referenced_region: region,
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueNotApplied(Loc { region, .. }))
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueAppliedToMultipleArgs(region))
|
||||
| Problem::RuntimeError(RuntimeError::ValueNotExposed { region, .. })
|
||||
| Problem::RuntimeError(RuntimeError::ModuleNotImported { region, .. })
|
||||
| Problem::RuntimeError(RuntimeError::InvalidPrecedence(_, region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedIdentifier(_, _, region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedTypeName(_, region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedClosure(region))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidRecordUpdate { region })
|
||||
| Problem::RuntimeError(RuntimeError::InvalidFloat(_, region, _))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidInt(_, _, region, _))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidInterpolation(region))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidHexadecimal(region))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidUnicodeCodePt(region))
|
||||
| Problem::RuntimeError(RuntimeError::EmptySingleQuote(region))
|
||||
| Problem::RuntimeError(RuntimeError::MultipleCharsInSingleQuote(region))
|
||||
| Problem::RuntimeError(RuntimeError::DegenerateBranch(region))
|
||||
| Problem::InvalidAliasRigid { region, .. }
|
||||
| Problem::InvalidInterpolation(region)
|
||||
| Problem::InvalidHexadecimal(region)
|
||||
| Problem::InvalidUnicodeCodePt(region)
|
||||
| Problem::NestedDatatype {
|
||||
def_region: region, ..
|
||||
}
|
||||
| Problem::InvalidExtensionType { region, .. }
|
||||
| Problem::AbilityHasTypeVariables {
|
||||
variables_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::HasClauseIsNotAbility { region }
|
||||
| Problem::IllegalHasClause { region }
|
||||
| Problem::DuplicateHasAbility { region, .. }
|
||||
| Problem::AbilityMemberMissingHasClause { region, .. }
|
||||
| Problem::AbilityMemberMultipleBoundVars {
|
||||
span_has_clauses: region,
|
||||
..
|
||||
}
|
||||
| Problem::AbilityNotOnToplevel { region }
|
||||
| Problem::AbilityUsedAsType(_, _, region)
|
||||
| Problem::NestedSpecialization(_, region)
|
||||
| Problem::IllegalDerivedAbility(region)
|
||||
| Problem::ImplementationNotFound { region, .. }
|
||||
| Problem::NotAnAbilityMember { region, .. }
|
||||
| Problem::OptionalAbilityImpl { region, .. }
|
||||
| Problem::QualifiedAbilityImpl { region }
|
||||
| Problem::AbilityImplNotIdent { region }
|
||||
| Problem::DuplicateImpl {
|
||||
original: region, ..
|
||||
}
|
||||
| Problem::NotAnAbility(region)
|
||||
| Problem::ImplementsNonRequired { region, .. }
|
||||
| Problem::DoesNotImplementAbility { region, .. }
|
||||
| Problem::NoIdentifiersIntroduced(region)
|
||||
| Problem::OverloadedSpecialization {
|
||||
overload: region, ..
|
||||
}
|
||||
| Problem::NotBoundInAllPatterns { region, .. }
|
||||
| Problem::SignatureDefMismatch {
|
||||
def_pattern: region,
|
||||
..
|
||||
}
|
||||
| Problem::MultipleListRestPattern { region }
|
||||
| Problem::UnnecessaryOutputWildcard { region } => Some(*region),
|
||||
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
|
||||
| Problem::BadRecursion(cycle_entries) => {
|
||||
cycle_entries.first().map(|entry| entry.expr_region)
|
||||
}
|
||||
Problem::RuntimeError(RuntimeError::UnresolvedTypeVar)
|
||||
| Problem::RuntimeError(RuntimeError::ErroneousType)
|
||||
| Problem::RuntimeError(RuntimeError::NonExhaustivePattern)
|
||||
| Problem::RuntimeError(RuntimeError::NoImplementation)
|
||||
| Problem::RuntimeError(RuntimeError::VoidValue)
|
||||
| Problem::RuntimeError(RuntimeError::ExposedButNotDefined(_))
|
||||
| Problem::RuntimeError(RuntimeError::NoImplementationNamed { .. })
|
||||
| Problem::ExposedButNotDefined(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ExtensionTypeKind {
|
||||
Record,
|
||||
|
|
|
@ -244,6 +244,7 @@ fn create_llvm_module<'a>(
|
|||
};
|
||||
let (main_fn_name, main_fn) = match config.mode {
|
||||
LlvmBackendMode::Binary => unreachable!(),
|
||||
LlvmBackendMode::BinaryDev => unreachable!(),
|
||||
LlvmBackendMode::CliTest => unreachable!(),
|
||||
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
|
||||
&env,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue