mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Merge pull request #4802 from roc-lang/valgrind-unit-tests
Valgrind unit tests
This commit is contained in:
commit
d8b2ff07f8
13 changed files with 547 additions and 64 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -5262,6 +5262,24 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valgrind"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"cli_utils",
|
||||
"indoc",
|
||||
"roc_build",
|
||||
"roc_cli",
|
||||
"roc_linker",
|
||||
"roc_load",
|
||||
"roc_mono",
|
||||
"roc_packaging",
|
||||
"roc_reporting",
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -17,6 +17,7 @@ members = [
|
|||
"crates/repl_wasm",
|
||||
"crates/repl_expect",
|
||||
"crates/test_utils",
|
||||
"crates/valgrind",
|
||||
"crates/tracing",
|
||||
"crates/utils",
|
||||
"crates/docs",
|
||||
|
@ -129,3 +130,7 @@ codegen-units = 1
|
|||
[profile.bench]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
|
|
|
@ -4,7 +4,7 @@ use roc_build::{
|
|||
legacy_host_filename, link, preprocess_host_wasm32, preprocessed_host_filename,
|
||||
rebuild_host, LinkType, LinkingStrategy,
|
||||
},
|
||||
program::{self, CodeGenOptions},
|
||||
program::{self, CodeGenBackend, CodeGenOptions},
|
||||
};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_load::{
|
||||
|
@ -18,7 +18,10 @@ use roc_reporting::{
|
|||
report::{RenderTarget, DEFAULT_PALETTE},
|
||||
};
|
||||
use roc_target::TargetInfo;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
path::Path,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::{path::PathBuf, thread::JoinHandle};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
|
@ -570,3 +573,56 @@ pub fn check_file<'a>(
|
|||
compilation_end,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn build_str_test<'a>(
|
||||
arena: &'a Bump,
|
||||
app_module_path: &Path,
|
||||
app_module_source: &'a str,
|
||||
assume_prebuild: bool,
|
||||
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
|
||||
let triple = target_lexicon::Triple::host();
|
||||
|
||||
let code_gen_options = CodeGenOptions {
|
||||
backend: CodeGenBackend::Llvm,
|
||||
opt_level: OptLevel::Normal,
|
||||
emit_debug_info: false,
|
||||
};
|
||||
|
||||
let emit_timings = false;
|
||||
let link_type = LinkType::Executable;
|
||||
let linking_strategy = LinkingStrategy::Surgical;
|
||||
let wasm_dev_stack_bytes = None;
|
||||
|
||||
let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
|
||||
let build_ordering = BuildOrdering::AlwaysBuild;
|
||||
let threading = Threading::AtMost(2);
|
||||
|
||||
let load_config = standard_load_config(&triple, build_ordering, threading);
|
||||
|
||||
let compilation_start = std::time::Instant::now();
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
PathBuf::from("valgrind_test.roc"),
|
||||
app_module_source,
|
||||
app_module_path.to_path_buf(),
|
||||
roc_cache_dir,
|
||||
load_config,
|
||||
)
|
||||
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
|
||||
|
||||
build_loaded_file(
|
||||
arena,
|
||||
&triple,
|
||||
app_module_path.to_path_buf(),
|
||||
code_gen_options,
|
||||
emit_timings,
|
||||
link_type,
|
||||
linking_strategy,
|
||||
assume_prebuild,
|
||||
wasm_dev_stack_bytes,
|
||||
loaded,
|
||||
compilation_start,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ use roc_load::{ExpectMetadata, LoadingProblem, Threading};
|
|||
use roc_mono::ir::OptLevel;
|
||||
use roc_packaging::cache::RocCacheDir;
|
||||
use roc_packaging::tarball::Compression;
|
||||
use roc_reporting::cli::Problems;
|
||||
use std::env;
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::io;
|
||||
|
@ -463,7 +462,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
"if there were errors, we would have already exited."
|
||||
);
|
||||
if problems.warnings > 0 {
|
||||
print_problems(problems, start_time.elapsed());
|
||||
problems.print_to_stdout(start_time.elapsed());
|
||||
println!(".\n\nRunning tests…\n\n\x1B[36m{}\x1B[39m", "─".repeat(80));
|
||||
}
|
||||
}
|
||||
|
@ -708,7 +707,7 @@ pub fn build(
|
|||
// since the process is about to exit anyway.
|
||||
// std::mem::forget(arena);
|
||||
|
||||
print_problems(problems, total_time);
|
||||
problems.print_to_stdout(total_time);
|
||||
println!(" while successfully building:\n\n {generated_filename}");
|
||||
|
||||
// Return a nonzero exit code if there were problems
|
||||
|
@ -716,7 +715,7 @@ pub fn build(
|
|||
}
|
||||
BuildAndRun => {
|
||||
if problems.errors > 0 || problems.warnings > 0 {
|
||||
print_problems(problems, total_time);
|
||||
problems.print_to_stdout(total_time);
|
||||
println!(
|
||||
".\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
|
||||
"─".repeat(80)
|
||||
|
@ -737,7 +736,7 @@ pub fn build(
|
|||
"if there are errors, they should have been returned as an error variant"
|
||||
);
|
||||
if problems.warnings > 0 {
|
||||
print_problems(problems, total_time);
|
||||
problems.print_to_stdout(total_time);
|
||||
println!(
|
||||
".\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
||||
"─".repeat(80)
|
||||
|
@ -771,7 +770,7 @@ fn handle_error_module(
|
|||
|
||||
let problems = roc_build::program::report_problems_typechecked(&mut module);
|
||||
|
||||
print_problems(problems, total_time);
|
||||
problems.print_to_stdout(total_time);
|
||||
|
||||
if print_run_anyway_hint {
|
||||
// If you're running "main.roc" then you can just do `roc run`
|
||||
|
@ -803,34 +802,6 @@ fn handle_loading_problem(problem: LoadingProblem) -> io::Result<i32> {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_problems(problems: Problems, total_time: std::time::Duration) {
|
||||
const GREEN: usize = 32;
|
||||
const YELLOW: usize = 33;
|
||||
|
||||
print!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms",
|
||||
match problems.errors {
|
||||
0 => GREEN,
|
||||
_ => YELLOW,
|
||||
},
|
||||
problems.errors,
|
||||
match problems.errors {
|
||||
1 => "error",
|
||||
_ => "errors",
|
||||
},
|
||||
match problems.warnings {
|
||||
0 => GREEN,
|
||||
_ => YELLOW,
|
||||
},
|
||||
problems.warnings,
|
||||
match problems.warnings {
|
||||
1 => "warning",
|
||||
_ => "warnings",
|
||||
},
|
||||
total_time.as_millis(),
|
||||
);
|
||||
}
|
||||
|
||||
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||
arena: &Bump,
|
||||
opt_level: OptLevel,
|
||||
|
|
|
@ -251,10 +251,10 @@ pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
|
|||
let mut cmd = Command::new("valgrind");
|
||||
let named_tempfile =
|
||||
NamedTempFile::new().expect("Unable to create tempfile for valgrind results");
|
||||
let filepath = named_tempfile.path().to_str().unwrap();
|
||||
|
||||
cmd.arg("--tool=memcheck");
|
||||
cmd.arg("--xml=yes");
|
||||
cmd.arg(format!("--xml-file={}", named_tempfile.path().display()));
|
||||
|
||||
// If you are having valgrind issues on MacOS, you may need to suppress some
|
||||
// of the errors. Read more here: https://github.com/roc-lang/roc/issues/746
|
||||
|
@ -274,8 +274,6 @@ pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
|
|||
}
|
||||
}
|
||||
|
||||
cmd.arg(format!("--xml-file={}", filepath));
|
||||
|
||||
for arg in args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
|
|
@ -4468,23 +4468,7 @@ fn build_header<'a>(
|
|||
//
|
||||
// Also build a list of imported_values_to_expose (like `bar` above.)
|
||||
for (qualified_module_name, exposed_idents, region) in imported.into_iter() {
|
||||
let cloned_module_name = qualified_module_name.module.clone();
|
||||
let pq_module_name = if qualified_module_name.is_builtin() {
|
||||
// If this is a builtin, it must be unqualified, and we should *never* prefix it
|
||||
// with the package shorthand! The user intended to import the module as-is here.
|
||||
debug_assert!(qualified_module_name.opt_package.is_none());
|
||||
PQModuleName::Unqualified(qualified_module_name.module)
|
||||
} else {
|
||||
match qualified_module_name.opt_package {
|
||||
None => match opt_shorthand {
|
||||
Some(shorthand) => {
|
||||
PQModuleName::Qualified(shorthand, qualified_module_name.module)
|
||||
}
|
||||
None => PQModuleName::Unqualified(qualified_module_name.module),
|
||||
},
|
||||
Some(package) => PQModuleName::Qualified(package, cloned_module_name),
|
||||
}
|
||||
};
|
||||
let pq_module_name = qualified_module_name.into_pq_module_name(opt_shorthand);
|
||||
|
||||
let module_id = module_ids.get_or_insert(&pq_module_name);
|
||||
|
||||
|
@ -4497,18 +4481,14 @@ fn build_header<'a>(
|
|||
// to the same symbols as the ones we're using here.
|
||||
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
|
||||
|
||||
for Loc {
|
||||
region,
|
||||
value: ident,
|
||||
} in exposed_idents
|
||||
{
|
||||
let ident_id = ident_ids.get_or_insert(ident.as_str());
|
||||
for loc_ident in exposed_idents {
|
||||
let ident_id = ident_ids.get_or_insert(loc_ident.value.as_str());
|
||||
let symbol = Symbol::new(module_id, ident_id);
|
||||
|
||||
// Since this value is exposed, add it to our module's default scope.
|
||||
debug_assert!(!scope.contains_key(&ident));
|
||||
debug_assert!(!scope.contains_key(&loc_ident.value));
|
||||
|
||||
scope.insert(ident, (symbol, region));
|
||||
scope.insert(loc_ident.value, (symbol, loc_ident.region));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
pub use roc_ident::IdentStr;
|
||||
use std::fmt;
|
||||
|
||||
use crate::symbol::PQModuleName;
|
||||
|
||||
/// This could be uppercase or lowercase, qualified or unqualified.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
||||
pub struct Ident(pub IdentStr);
|
||||
|
@ -21,6 +23,25 @@ pub struct QualifiedModuleName<'a> {
|
|||
pub module: ModuleName,
|
||||
}
|
||||
|
||||
impl<'a> QualifiedModuleName<'a> {
|
||||
pub fn into_pq_module_name(self, opt_shorthand: Option<&'a str>) -> PQModuleName<'a> {
|
||||
if self.is_builtin() {
|
||||
// If this is a builtin, it must be unqualified, and we should *never* prefix it
|
||||
// with the package shorthand! The user intended to import the module as-is here.
|
||||
debug_assert!(self.opt_package.is_none());
|
||||
PQModuleName::Unqualified(self.module)
|
||||
} else {
|
||||
match self.opt_package {
|
||||
None => match opt_shorthand {
|
||||
Some(shorthand) => PQModuleName::Qualified(shorthand, self.module),
|
||||
None => PQModuleName::Unqualified(self.module),
|
||||
},
|
||||
Some(package) => PQModuleName::Qualified(package, self.module),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ModuleName(IdentStr);
|
||||
|
||||
|
|
|
@ -20,6 +20,34 @@ impl Problems {
|
|||
self.warnings.min(1) as i32
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_to_stdout(&self, total_time: std::time::Duration) {
|
||||
const GREEN: usize = 32;
|
||||
const YELLOW: usize = 33;
|
||||
|
||||
print!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms",
|
||||
match self.errors {
|
||||
0 => GREEN,
|
||||
_ => YELLOW,
|
||||
},
|
||||
self.errors,
|
||||
match self.errors {
|
||||
1 => "error",
|
||||
_ => "errors",
|
||||
},
|
||||
match self.warnings {
|
||||
0 => GREEN,
|
||||
_ => YELLOW,
|
||||
},
|
||||
self.warnings,
|
||||
match self.warnings {
|
||||
1 => "warning",
|
||||
_ => "warnings",
|
||||
},
|
||||
total_time.as_millis(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_problems(
|
||||
|
|
20
crates/valgrind/Cargo.toml
Normal file
20
crates/valgrind/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "valgrind"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
roc_cli = { path = "../cli" }
|
||||
cli_utils = { path = "../cli_utils" }
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_linker = { path = "../linker" }
|
||||
roc_packaging = { path = "../packaging" }
|
||||
bumpalo.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
tempfile.workspace = true
|
||||
indoc.workspace = true
|
237
crates/valgrind/src/lib.rs
Normal file
237
crates/valgrind/src/lib.rs
Normal file
|
@ -0,0 +1,237 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use indoc::indoc;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
static BUILD_ONCE: std::sync::Once = std::sync::Once::new();
|
||||
|
||||
#[cfg(all(target_os = "linux"))]
|
||||
fn build_host() {
|
||||
use roc_build::link::preprocessed_host_filename;
|
||||
use roc_linker::build_and_preprocess_host;
|
||||
|
||||
let platform_main_roc = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("zig-platform/main.roc");
|
||||
|
||||
// tests always run on the host
|
||||
let target = target_lexicon::Triple::host();
|
||||
|
||||
// the preprocessed host is stored beside the platform's main.roc
|
||||
let preprocessed_host_path =
|
||||
platform_main_roc.with_file_name(preprocessed_host_filename(&target).unwrap());
|
||||
|
||||
build_and_preprocess_host(
|
||||
roc_mono::ir::OptLevel::Normal,
|
||||
&target,
|
||||
&platform_main_roc,
|
||||
&preprocessed_host_path,
|
||||
vec![String::from("mainForHost")],
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
fn valgrind_test(source: &str) {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
valgrind_test_linux(source)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let _ = source;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn valgrind_test_linux(source: &str) {
|
||||
use roc_cli::build::BuiltFile;
|
||||
|
||||
// the host is identical for all tests so we only want to build it once
|
||||
BUILD_ONCE.call_once(build_host);
|
||||
|
||||
let pf = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("zig-platform/main.roc");
|
||||
|
||||
assert!(pf.exists(), "cannot find platform {:?}", &pf);
|
||||
|
||||
let mut app_module_source = format!(
|
||||
indoc::indoc!(
|
||||
r#"
|
||||
app "test"
|
||||
packages {{ pf: "{}" }}
|
||||
imports []
|
||||
provides [main] to pf
|
||||
|
||||
main =
|
||||
"#
|
||||
),
|
||||
pf.to_str().unwrap()
|
||||
);
|
||||
|
||||
for line in source.lines() {
|
||||
app_module_source.push_str(" ");
|
||||
app_module_source.push_str(line);
|
||||
app_module_source.push('\n');
|
||||
}
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let app_module_path = temp_dir.path().join("app.roc");
|
||||
|
||||
let arena = bumpalo::Bump::new();
|
||||
let assume_prebuilt = true;
|
||||
let res_binary_path = roc_cli::build::build_str_test(
|
||||
&arena,
|
||||
&app_module_path,
|
||||
&app_module_source,
|
||||
assume_prebuilt,
|
||||
);
|
||||
|
||||
match res_binary_path {
|
||||
Ok(BuiltFile {
|
||||
binary_path,
|
||||
problems,
|
||||
total_time: _,
|
||||
expect_metadata: _,
|
||||
}) => {
|
||||
if problems.exit_code() != 0 {
|
||||
panic!("there are problems")
|
||||
}
|
||||
|
||||
run_with_valgrind(&binary_path);
|
||||
}
|
||||
Err(roc_cli::build::BuildFileError::LoadingProblem(
|
||||
roc_load::LoadingProblem::FormattedReport(report),
|
||||
)) => {
|
||||
eprintln!("{}", report);
|
||||
panic!("");
|
||||
}
|
||||
Err(e) => panic!("{:?}", e),
|
||||
}
|
||||
|
||||
drop(temp_dir)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn run_with_valgrind(binary_path: &std::path::Path) {
|
||||
use cli_utils::helpers::{extract_valgrind_errors, ValgrindError, ValgrindErrorXWhat};
|
||||
|
||||
// If possible, report the generated executable name relative to the current dir.
|
||||
let generated_filename = binary_path
|
||||
.strip_prefix(std::env::current_dir().unwrap())
|
||||
.unwrap_or(binary_path)
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
let (valgrind_out, raw_xml) =
|
||||
cli_utils::helpers::run_with_valgrind([], &[generated_filename.to_string()]);
|
||||
|
||||
if valgrind_out.status.success() {
|
||||
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
indoc!(
|
||||
r#"
|
||||
failed to parse the `valgrind` xml output:
|
||||
|
||||
Error was:
|
||||
|
||||
{:?}
|
||||
|
||||
valgrind xml was:
|
||||
|
||||
{}
|
||||
|
||||
valgrind stdout was:
|
||||
|
||||
{}
|
||||
|
||||
valgrind stderr was:
|
||||
|
||||
{}
|
||||
"#
|
||||
),
|
||||
err, raw_xml, valgrind_out.stdout, valgrind_out.stderr
|
||||
);
|
||||
});
|
||||
|
||||
if !memory_errors.is_empty() {
|
||||
for error in memory_errors {
|
||||
let ValgrindError {
|
||||
kind,
|
||||
what: _,
|
||||
xwhat,
|
||||
} = error;
|
||||
println!("Valgrind Error: {}\n", kind);
|
||||
|
||||
if let Some(ValgrindErrorXWhat {
|
||||
text,
|
||||
leakedbytes: _,
|
||||
leakedblocks: _,
|
||||
}) = xwhat
|
||||
{
|
||||
println!(" {}", text);
|
||||
}
|
||||
}
|
||||
panic!("Valgrind found memory errors");
|
||||
}
|
||||
} else {
|
||||
let exit_code = match valgrind_out.status.code() {
|
||||
Some(code) => format!("exit code {}", code),
|
||||
None => "no exit code".to_string(),
|
||||
};
|
||||
|
||||
panic!(
|
||||
"`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"",
|
||||
exit_code, valgrind_out.stdout, valgrind_out.stderr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_concat_consumes_first_argument() {
|
||||
valgrind_test("List.concat (List.withCapacity 1024) [1,2,3] |> List.len |> Num.toStr");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_capacity_concat() {
|
||||
valgrind_test(r#"Str.withCapacity 42 |> Str.concat "foobar""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_scalar() {
|
||||
valgrind_test(indoc!(
|
||||
r#"
|
||||
Str.appendScalar "abcd" 'A'
|
||||
|> Result.withDefault ""
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_not_present() {
|
||||
valgrind_test(indoc!(
|
||||
r#"
|
||||
Str.split (Str.concat "a string that is stored on the heap" "!") "\n"
|
||||
|> Str.joinWith ""
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_concat_first_argument_not_unique() {
|
||||
valgrind_test(indoc!(
|
||||
r#"
|
||||
(
|
||||
str1 = Str.reserve "" 48
|
||||
str2 = "a"
|
||||
|
||||
out = Str.concat str1 str2
|
||||
if Bool.false then
|
||||
out
|
||||
else
|
||||
str1
|
||||
)
|
||||
"#
|
||||
));
|
||||
}
|
2
crates/valgrind/zig-platform/.gitignore
vendored
Normal file
2
crates/valgrind/zig-platform/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
dynhost
|
||||
libapp.so
|
138
crates/valgrind/zig-platform/host.zig
Normal file
138
crates/valgrind/zig-platform/host.zig
Normal file
|
@ -0,0 +1,138 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const str = @import("str");
|
||||
const RocStr = str.RocStr;
|
||||
const testing = std.testing;
|
||||
const expectEqual = testing.expectEqual;
|
||||
const expect = testing.expect;
|
||||
|
||||
comptime {
|
||||
// This is a workaround for https://github.com/ziglang/zig/issues/8218
|
||||
// which is only necessary on macOS.
|
||||
//
|
||||
// Once that issue is fixed, we can undo the changes in
|
||||
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
|
||||
// -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!
|
||||
if (builtin.os.tag == .macos) {
|
||||
_ = @import("compiler_rt");
|
||||
}
|
||||
}
|
||||
|
||||
const Align = 2 * @alignOf(usize);
|
||||
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
|
||||
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
|
||||
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
|
||||
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
|
||||
|
||||
const DEBUG: bool = false;
|
||||
|
||||
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
|
||||
if (DEBUG) {
|
||||
var ptr = malloc(size);
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
|
||||
return ptr;
|
||||
} else {
|
||||
return malloc(size);
|
||||
}
|
||||
}
|
||||
|
||||
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
|
||||
if (DEBUG) {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
|
||||
}
|
||||
|
||||
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
|
||||
}
|
||||
|
||||
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
|
||||
if (DEBUG) {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
|
||||
}
|
||||
|
||||
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
|
||||
}
|
||||
|
||||
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
|
||||
_ = tag_id;
|
||||
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
const msg = @ptrCast([*:0]const u8, c_ptr);
|
||||
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||
std.process.exit(0);
|
||||
}
|
||||
|
||||
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
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_getppid_windows_stub() callconv(.C) c_int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
|
||||
if (builtin.os.tag == .windows) {
|
||||
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub fn main() u8 {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
var timer = std.time.Timer.start() catch unreachable;
|
||||
|
||||
// actually call roc to populate the callresult
|
||||
var callresult = RocStr.empty();
|
||||
roc__mainForHost_1_exposed_generic(&callresult);
|
||||
|
||||
const nanos = timer.read();
|
||||
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
|
||||
|
||||
// stdout the result
|
||||
stdout.print("{s}", .{callresult.asSlice()}) catch unreachable;
|
||||
|
||||
callresult.deinit();
|
||||
|
||||
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
|
||||
|
||||
return 0;
|
||||
}
|
9
crates/valgrind/zig-platform/main.roc
Normal file
9
crates/valgrind/zig-platform/main.roc
Normal file
|
@ -0,0 +1,9 @@
|
|||
platform "echo-in-zig"
|
||||
requires {} { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [mainForHost]
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
Loading…
Add table
Add a link
Reference in a new issue