Merge pull request #4220 from roc-lang/roc-dev-inline-expects

Roc dev inline expects
This commit is contained in:
Richard Feldman 2022-11-05 14:54:26 -07:00 committed by GitHub
commit 0a37431f36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 861 additions and 161 deletions

View File

@ -18,6 +18,7 @@ jobs:
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'yes'
check-modified-files-only: 'yes'
base-branch: 'main'
check-modified-files-only: 'yes'
if: ${{ github.event_name == 'pull_request' }}

2
Cargo.lock generated
View File

@ -3413,6 +3413,7 @@ dependencies = [
"roc_fmt",
"roc_gen_llvm",
"roc_glue",
"roc_intern",
"roc_linker",
"roc_load",
"roc_module",
@ -3960,6 +3961,7 @@ dependencies = [
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_gen_llvm",
"roc_intern",
"roc_load",

View File

@ -60,6 +60,7 @@ roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true }
roc_tracing = { path = "../tracing" }
roc_intern = { path = "../compiler/intern" }
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
const_format = { version = "0.2.23", features = ["const_generics"] }
bumpalo = { version = "3.11.0", features = ["collections"] }

View File

@ -1,15 +1,13 @@
use bumpalo::Bump;
use roc_build::{
link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy},
program::{self, Problems},
program::{self, CodeGenOptions, Problems},
};
use roc_builtins::bitcode;
use roc_collections::VecMap;
use roc_load::{
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadMonomorphizedError, LoadedModule,
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, Threading,
};
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo;
@ -30,12 +28,11 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
.unwrap()
}
pub struct BuiltFile {
pub struct BuiltFile<'a> {
pub binary_path: PathBuf,
pub problems: Problems,
pub total_time: Duration,
pub expectations: VecMap<ModuleId, Expectations>,
pub interns: Interns,
pub expect_metadata: ExpectMetadata<'a>,
}
pub enum BuildOrdering {
@ -60,8 +57,7 @@ pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
opt_level: OptLevel,
emit_debug_info: bool,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
@ -69,7 +65,7 @@ pub fn build_file<'a>(
threading: Threading,
wasm_dev_stack_bytes: Option<u32>,
order: BuildOrdering,
) -> Result<BuiltFile, BuildFileError<'a>> {
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
let target_info = TargetInfo::from(target);
@ -121,7 +117,7 @@ pub fn build_file<'a>(
match roc_target::OperatingSystem::from(target.operating_system) {
Wasi => {
if matches!(opt_level, OptLevel::Development) {
if matches!(code_gen_options.opt_level, OptLevel::Development) {
("wasm", "wasm", Some("wasm"))
} else {
("zig", "bc", Some("wasm"))
@ -180,7 +176,7 @@ pub fn build_file<'a>(
};
let rebuild_thread = spawn_rebuild_thread(
opt_level,
code_gen_options.opt_level,
linking_strategy,
prebuilt,
host_input_path.clone(),
@ -241,11 +237,8 @@ pub fn build_file<'a>(
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
let problems = program::report_problems_monomorphized(&mut loaded);
let expectations = std::mem::take(&mut loaded.expectations);
let loaded = loaded;
let interns = loaded.interns.clone();
enum HostRebuildTiming {
BeforeApp(u128),
ConcurrentWithApp(JoinHandle<u128>),
@ -266,13 +259,12 @@ pub fn build_file<'a>(
HostRebuildTiming::ConcurrentWithApp(rebuild_thread)
};
let (roc_app_bytes, code_gen_timing) = program::gen_from_mono_module(
let (roc_app_bytes, code_gen_timing, expect_metadata) = program::gen_from_mono_module(
arena,
loaded,
&app_module_path,
target,
opt_level,
emit_debug_info,
code_gen_options,
&preprocessed_host_path,
wasm_dev_stack_bytes,
);
@ -351,7 +343,7 @@ pub fn build_file<'a>(
let str_host_obj_path = bitcode::get_builtins_host_obj_path();
if matches!(opt_level, OptLevel::Development) {
if matches!(code_gen_options.backend, program::CodeGenBackend::Assembly) {
inputs.push(&str_host_obj_path);
}
@ -388,8 +380,7 @@ pub fn build_file<'a>(
binary_path,
problems,
total_time,
interns,
expectations,
expect_metadata,
})
}

View File

@ -7,11 +7,9 @@ use build::BuiltFile;
use bumpalo::Bump;
use clap::{Arg, ArgMatches, Command, ValueSource};
use roc_build::link::{LinkType, LinkingStrategy};
use roc_build::program::Problems;
use roc_collections::VecMap;
use roc_build::program::{CodeGenBackend, CodeGenOptions, Problems};
use roc_error_macros::{internal_error, user_error};
use roc_load::{Expectations, LoadingProblem, Threading};
use roc_module::symbol::{Interns, ModuleId};
use roc_load::{ExpectMetadata, LoadingProblem, Threading};
use roc_mono::ir::OptLevel;
use std::env;
use std::ffi::{CString, OsStr};
@ -432,7 +430,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let mut writer = std::io::stdout();
let (failed, passed) = roc_repl_expect::run::run_expects(
let (failed, passed) = roc_repl_expect::run::run_toplevel_expects(
&mut writer,
roc_reporting::report::RenderTarget::ColorTerminal,
arena,
@ -481,18 +479,33 @@ pub fn build(
use build::build_file;
use BuildConfig::*;
let arena = Bump::new();
let filename = matches.value_of_os(ROC_FILE).unwrap();
let opt_level = match (
matches.is_present(FLAG_OPTIMIZE),
matches.is_present(FLAG_OPT_SIZE),
matches.is_present(FLAG_DEV),
) {
(true, false, false) => OptLevel::Optimize,
(false, true, false) => OptLevel::Size,
(false, false, true) => OptLevel::Development,
(false, false, false) => OptLevel::Normal,
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
// the process will end after this function,
// so we don't want to spend time freeing these values
let arena = ManuallyDrop::new(Bump::new());
let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
CodeGenBackend::Wasm
} else {
match matches.is_present(FLAG_DEV) {
true => CodeGenBackend::Assembly,
false => CodeGenBackend::Llvm,
}
};
let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config {
OptLevel::Development
} else {
match (
matches.is_present(FLAG_OPTIMIZE),
matches.is_present(FLAG_OPT_SIZE),
) {
(true, false) => OptLevel::Optimize,
(false, true) => OptLevel::Size,
(false, false) => OptLevel::Normal,
(true, true) => {
user_error!("build can be only one of `--optimize` and `--opt-size`")
}
}
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
@ -508,7 +521,7 @@ pub fn build(
};
let wasm_dev_backend = matches!(opt_level, OptLevel::Development)
&& matches!(triple.architecture, Architecture::Wasm32);
&& matches!(code_gen_backend, CodeGenBackend::Wasm);
let linking_strategy = if wasm_dev_backend {
LinkingStrategy::Additive
@ -528,6 +541,8 @@ pub fn build(
// We make an exception for Wasm, because cross-compiling is the norm in that case.
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
};
let filename = matches.value_of_os(ROC_FILE).unwrap();
let path = Path::new(filename);
// Spawn the root task
@ -559,12 +574,18 @@ pub fn build(
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
_ => BuildOrdering::AlwaysBuild,
};
let code_gen_options = CodeGenOptions {
backend: code_gen_backend,
opt_level,
emit_debug_info,
};
let res_binary_path = build_file(
&arena,
&triple,
path.to_path_buf(),
opt_level,
emit_debug_info,
code_gen_options,
emit_timings,
link_type,
linking_strategy,
@ -579,8 +600,7 @@ pub fn build(
binary_path,
problems,
total_time,
expectations,
interns,
expect_metadata,
}) => {
match config {
BuildOnly => {
@ -593,7 +613,7 @@ pub fn build(
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
// std::mem::forget(arena);
print_problems(problems, total_time);
println!(" while successfully building:\n\n {generated_filename}");
@ -616,7 +636,7 @@ pub fn build(
// ManuallyDrop will leak the bytes because we don't drop manually
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
roc_run(&arena, opt_level, triple, args, bytes, expect_metadata)
}
BuildAndRunIfNoErrors => {
debug_assert!(
@ -637,7 +657,7 @@ pub fn build(
// ManuallyDrop will leak the bytes because we don't drop manually
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
roc_run(&arena, opt_level, triple, args, bytes, expect_metadata)
}
}
}
@ -703,13 +723,12 @@ fn print_problems(problems: Problems, total_time: std::time::Duration) {
}
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
arena: &Bump,
opt_level: OptLevel,
triple: Triple,
args: I,
binary_bytes: &[u8],
expectations: VecMap<ModuleId, Expectations>,
interns: Interns,
expect_metadata: ExpectMetadata,
) -> io::Result<i32> {
match triple.architecture {
Architecture::Wasm32 => {
@ -720,10 +739,6 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(path);
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
#[cfg(target_family = "unix")]
{
use std::os::unix::ffi::OsStrExt;
@ -748,7 +763,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
Ok(0)
}
_ => roc_run_native(arena, opt_level, args, binary_bytes, expectations, interns),
_ => roc_run_native(arena, opt_level, args, binary_bytes, expect_metadata),
}
}
@ -848,35 +863,32 @@ fn make_argv_envp_windows<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
/// Run on the native OS (not on wasm)
#[cfg(target_family = "unix")]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: Bump,
arena: &Bump,
opt_level: OptLevel,
args: I,
binary_bytes: &[u8],
expectations: VecMap<ModuleId, Expectations>,
interns: Interns,
expect_metadata: ExpectMetadata,
) -> std::io::Result<i32> {
use bumpalo::collections::CollectIn;
unsafe {
let executable = roc_run_executable_file_path(binary_bytes)?;
let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args);
let (argv_cstrings, envp_cstrings) = make_argv_envp(arena, &executable, args);
let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
match opt_level {
OptLevel::Development => {
roc_run_native_debug(executable, &argv, &envp, expectations, interns)
}
OptLevel::Development => roc_dev_native(arena, executable, argv, envp, expect_metadata),
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
roc_run_native_fast(executable, &argv, &envp);
}
@ -950,14 +962,76 @@ impl ExecutableFile {
// with Expect
#[cfg(target_family = "unix")]
unsafe fn roc_run_native_debug(
_executable: ExecutableFile,
_argv: &[*const c_char],
_envp: &[*const c_char],
_expectations: VecMap<ModuleId, Expectations>,
_interns: Interns,
) {
todo!()
fn roc_dev_native(
arena: &Bump,
executable: ExecutableFile,
argv: bumpalo::collections::Vec<*const c_char>,
envp: bumpalo::collections::Vec<*const c_char>,
expect_metadata: ExpectMetadata,
) -> ! {
use roc_repl_expect::run::ExpectMemory;
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
let ExpectMetadata {
mut expectations,
interns,
layout_interner,
} = expect_metadata;
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
// let shm_name =
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
let memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
let layout_interner = layout_interner.into_global();
let mut writer = std::io::stdout();
match unsafe { libc::fork() } {
0 => unsafe {
// we are the child
executable.execve(&argv, &envp);
// Display a human-friendly error message
println!("Error {:?}", std::io::Error::last_os_error());
std::process::exit(1);
},
-1 => {
// something failed
// Display a human-friendly error message
println!("Error {:?}", std::io::Error::last_os_error());
std::process::exit(1)
}
1.. => {
for sig in &mut signals {
match sig {
SIGCHLD => break,
SIGUSR1 => {
// this is the signal we use for an expect failure. Let's see what the child told us
roc_repl_expect::run::render_expects_in_memory(
&mut writer,
arena,
&mut expectations,
&interns,
&layout_interner,
&memory,
)
.unwrap();
}
_ => println!("received signal {}", sig),
}
}
std::process::exit(0)
}
_ => unreachable!(),
}
}
#[cfg(target_os = "linux")]
@ -1036,12 +1110,11 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
/// Run on the native OS (not on wasm)
#[cfg(not(target_family = "unix"))]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
arena: &Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
opt_level: OptLevel,
args: I,
binary_bytes: &[u8],
_expectations: VecMap<ModuleId, Expectations>,
_interns: Interns,
_expect_metadata: ExpectMetadata,
) -> io::Result<i32> {
use bumpalo::collections::CollectIn;
@ -1055,18 +1128,18 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
match opt_level {
OptLevel::Development => {
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
todo!()
internal_error!("running `expect`s does not currently work on windows")
}
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
roc_run_native_fast(executable, &argv, &envp);

View File

@ -65,6 +65,34 @@ export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
std.process.exit(0);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() i32 {

View File

@ -64,6 +64,34 @@ export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
std.process.exit(0);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() i32 {

View File

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
@ -13,7 +14,6 @@ comptime {
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
const builtin = @import("builtin");
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
@ -81,6 +81,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
pub export fn main() u8 {
const stdout = std.io.getStdOut().writer();

View File

@ -80,6 +80,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
// warning! the array is currently stack-allocated so don't make this too big
const NUM_NUMS = 100;

View File

@ -1,6 +1,6 @@
const std = @import("std");
const str = @import("str");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -85,9 +85,39 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() callconv(.C) u8 {
pub fn main() !u8 {
const stderr = std.io.getStdErr().writer();
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
@ -108,7 +138,6 @@ pub export fn main() callconv(.C) u8 {
const nanos = timer.read();
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
const stderr = std.io.getStdErr().writer();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
return 0;

View File

@ -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,
},
)
}

View File

@ -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);
}
}

View File

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

View File

@ -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 });
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -15,8 +15,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)]

View File

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

View File

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

View File

@ -76,13 +76,23 @@ fn collect_roc_definitions<'a>(object: &object::File<'a, &'a [u8]>) -> MutMap<St
let address = sym.address() as u64;
// special exceptions for memcpy and memset.
if name == "roc_memcpy" {
vaddresses.insert("memcpy".to_string(), address);
} else if name == "roc_memset" {
vaddresses.insert("memset".to_string(), address);
} else if name == "roc_memmove" {
vaddresses.insert("memmove".to_string(), address);
// special exceptions for roc_ functions that map to libc symbols
let direct_mapping = match name {
"roc_memcpy" => Some("memcpy"),
"roc_memset" => Some("memset"),
"roc_memmove" => Some("memmove"),
// for expects
"roc_mmap" => Some("mmap"),
"roc_getppid" => Some("getppid"),
"roc_send_signal" => Some("kill"),
"roc_shm_open" => Some("shm_open"),
_ => None,
};
if let Some(libc_symbol) = direct_mapping {
vaddresses.insert(libc_symbol.to_string(), address);
}
vaddresses.insert(name.to_string(), address);

View File

@ -25,6 +25,7 @@ roc_types = {path = "../compiler/types"}
roc_gen_llvm = {path = "../compiler/gen_llvm"}
roc_region = { path = "../compiler/region" }
roc_build = { path = "../compiler/build" }
roc_error_macros = { path = "../error_macros" }
libloading = "0.7.1"
inkwell = { path = "../vendor/inkwell" }

View File

@ -6,6 +6,7 @@ use inkwell::context::Context;
use roc_build::link::llvm_module_to_dylib;
use roc_can::expr::ExpectLookup;
use roc_collections::{MutSet, VecMap};
use roc_error_macros::internal_error;
use roc_gen_llvm::{
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
run_roc::RocCallResult,
@ -21,7 +22,7 @@ use roc_target::TargetInfo;
use roc_types::subs::{Subs, Variable};
use target_lexicon::Triple;
pub(crate) struct ExpectMemory<'a> {
pub struct ExpectMemory<'a> {
ptr: *mut u8,
length: usize,
shm_name: Option<std::ffi::CString>,
@ -41,7 +42,7 @@ impl<'a> ExpectMemory<'a> {
}
}
fn create_or_reuse_mmap(shm_name: &str) -> Self {
pub fn create_or_reuse_mmap(shm_name: &str) -> Self {
let cstring = std::ffi::CString::new(shm_name).unwrap();
Self::mmap_help(cstring, libc::O_RDWR | libc::O_CREAT)
}
@ -54,19 +55,38 @@ impl<'a> ExpectMemory<'a> {
fn mmap_help(cstring: std::ffi::CString, shm_flags: i32) -> Self {
let ptr = unsafe {
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666);
if shared_fd == -1 {
internal_error!("failed to shm_open fd");
}
libc::ftruncate(shared_fd, Self::SHM_SIZE as _);
// NOTE: we can only call `ftruncate` once on this file descriptor on mac
if libc::ftruncate(shared_fd, Self::SHM_SIZE as _) == -1 {
internal_error!("failed to truncate shared file, are the permissions wrong?");
}
libc::mmap(
let ptr = libc::mmap(
std::ptr::null_mut(),
Self::SHM_SIZE,
libc::PROT_WRITE | libc::PROT_READ,
libc::MAP_SHARED,
shared_fd,
0,
)
);
if ptr as usize == usize::MAX {
// ptr = -1
roc_error_macros::internal_error!("failed to mmap shared pointer")
}
// fill the buffer with a fill pattern
libc::memset(ptr, 0xAA, Self::SHM_SIZE);
ptr
};
// puts in the initial header
let _ = ExpectSequence::new(ptr as *mut u8);
Self {
ptr: ptr.cast(),
length: Self::SHM_SIZE,
@ -83,7 +103,34 @@ impl<'a> ExpectMemory<'a> {
}
#[allow(clippy::too_many_arguments)]
pub fn run_expects<'a, W: std::io::Write>(
pub fn run_inline_expects<'a, W: std::io::Write>(
writer: &mut W,
render_target: RenderTarget,
arena: &'a Bump,
interns: &'a Interns,
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
lib: &libloading::Library,
expectations: &mut VecMap<ModuleId, Expectations>,
expects: ExpectFunctions<'_>,
) -> std::io::Result<(usize, usize)> {
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
let mut memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
run_expects_with_memory(
writer,
render_target,
arena,
interns,
layout_interner,
lib,
expectations,
expects,
&mut memory,
)
}
#[allow(clippy::too_many_arguments)]
pub fn run_toplevel_expects<'a, W: std::io::Write>(
writer: &mut W,
render_target: RenderTarget,
arena: &'a Bump,
@ -325,14 +372,16 @@ fn run_expect_fx<'a, W: std::io::Write>(
}
}
pub fn roc_dev_expect<'a>(
pub fn render_expects_in_memory<'a>(
writer: &mut impl std::io::Write,
arena: &'a Bump,
expectations: &mut VecMap<ModuleId, Expectations>,
interns: &'a Interns,
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
shared_ptr: *mut u8,
memory: &ExpectMemory,
) -> std::io::Result<usize> {
let shared_ptr = memory.ptr;
let frame = ExpectFrame::at_offset(shared_ptr, ExpectSequence::START_OFFSET);
let module_id = frame.module_id;

View File

@ -73,6 +73,41 @@ pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
}
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
fn print_backtrace() {
eprintln!("Here is the call stack that led to the crash:\n");
@ -263,7 +298,7 @@ pub extern "C" fn roc_fx_exePath(roc_str: &RocStr) -> RocResult<RocList<u8>, ()>
#[no_mangle]
pub extern "C" fn roc_fx_stdinLine() -> RocStr {
use std::io::{self, BufRead};
use std::io::BufRead;
let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();

View File

@ -1,6 +1,6 @@
const std = @import("std");
const str = @import("str");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -85,6 +85,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() u8 {

View File

@ -2,7 +2,7 @@
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::{ManuallyDrop, MaybeUninit};
use core::mem::MaybeUninit;
use libc;
use roc_std::{RocList, RocStr};
use std::env;
@ -72,6 +72,41 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
libc::memset(dst, c, n)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let arg = env::args()
@ -122,9 +157,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
#[no_mangle]
pub extern "C" fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
let stdin = std::io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from(line1.as_str())
@ -132,11 +165,10 @@ pub extern "C" fn roc_fx_getLine() -> RocStr {
#[no_mangle]
pub extern "C" fn roc_fx_getChar() -> u8 {
use std::io::{self, BufRead};
let mut buffer = [0];
if let Err(ioerr) = io::stdin().lock().read_exact(&mut buffer[..]) {
if ioerr.kind() == io::ErrorKind::UnexpectedEof {
if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) {
if ioerr.kind() == std::io::ErrorKind::UnexpectedEof {
u8::MAX
} else {
panic!("Got an unexpected error while reading char from stdin");

View File

@ -1,6 +1,6 @@
const std = @import("std");
const str = @import("str");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -144,6 +144,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() callconv(.C) u8 {

View File

@ -95,6 +95,11 @@ void *roc_memset(void *str, int c, size_t n) {
return memset(str, c, n);
}
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
int roc_getppid() { return getppid(); }
struct RocStr {
char *bytes;
size_t len;

View File

@ -27,6 +27,12 @@ void* roc_memcpy(void* dest, const void* src, size_t n) {
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
int roc_getppid() { return getppid(); }
struct RocStr {
char* bytes;
size_t len;

View File

@ -54,6 +54,41 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
libc::memset(dst, c, n)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let mut roc_str = RocStr::default();

View File

@ -75,6 +75,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
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 roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn 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 {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const mem = std.mem;
const Allocator = mem.Allocator;
@ -103,4 +131,4 @@ pub fn main() u8 {
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
return 0;
}
}

View File

@ -27,6 +27,11 @@ void* roc_memcpy(void* dest, const void* src, size_t n) {
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
int roc_getppid() { return getppid(); }
struct RocStr {
char* bytes;
size_t len;

View File

@ -33,6 +33,41 @@ pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let args: Vec<String> = env::args().collect();