mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Merge remote-tracking branch 'origin/trunk' into expect-dont-panic
This commit is contained in:
commit
a55ff62e6c
501 changed files with 26570 additions and 12276 deletions
369
compiler/test_gen/src/helpers/debug-wasm-test.html
Normal file
369
compiler/test_gen/src/helpers/debug-wasm-test.html
Normal file
|
@ -0,0 +1,369 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #00c000;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
section {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
h1 {
|
||||
margin: 32px auto;
|
||||
}
|
||||
li {
|
||||
margin: 8px;
|
||||
}
|
||||
code {
|
||||
color: #0080ff;
|
||||
background-color: #000;
|
||||
padding: 1px 4px;
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
input,
|
||||
button {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
small {
|
||||
font-style: italic;
|
||||
}
|
||||
#error {
|
||||
color: #f00;
|
||||
}
|
||||
.controls {
|
||||
margin-top: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.controls button {
|
||||
margin: 16px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.row-file label {
|
||||
margin: 0 32px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<h1>Debug Wasm tests in the browser!</h1>
|
||||
<p>
|
||||
You can step through the generated code instruction-by-instruction, and
|
||||
examine memory contents
|
||||
</p>
|
||||
<h3>Steps</h3>
|
||||
<ul>
|
||||
<li>
|
||||
In <code>gen_wasm/src/lib.rs</code>, set
|
||||
<code>DEBUG_LOG_SETTINGS.keep_test_binary = true</code>
|
||||
</li>
|
||||
<li>Run <code>cargo test-gen-wasm -- my_test --nocapture</code></li>
|
||||
<li>
|
||||
Look for the path written to the console for
|
||||
<code>final.wasm</code> and select it in the file picker below
|
||||
</li>
|
||||
<li>
|
||||
Open the browser DevTools <br />
|
||||
<small> Control+Shift+I or Command+Option+I or F12 </small>
|
||||
</li>
|
||||
<li>
|
||||
Click one of the buttons below, depending on what kind of test it is.
|
||||
<br />
|
||||
<small>
|
||||
Only one of them will work. The other will probably crash or
|
||||
something.
|
||||
</small>
|
||||
</li>
|
||||
<li>
|
||||
The debugger should pause just before entering the first Wasm call.
|
||||
Step into a couple of Wasm calls until you reach your test code in
|
||||
<code>$#UserApp_main_1</code>
|
||||
</li>
|
||||
<li>
|
||||
Chrome DevTools now has a Memory Inspector panel! In the debugger,
|
||||
find <code>Module -> memories -> $memory</code>. Right click and
|
||||
select "Reveal in Memory Inspector"
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="controls">
|
||||
<div class="row row-file">
|
||||
<label for="wasm-file">Select final.wasm</label>
|
||||
<input id="wasm-file" type="file" />
|
||||
</div>
|
||||
<div id="error" class="row"></div>
|
||||
<div class="row">
|
||||
<button id="button-expression">Run as Roc expression test</button>
|
||||
<button id="button-refcount">Run as reference counting test</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
const file_input = document.getElementById("wasm-file");
|
||||
const button_expression = document.getElementById("button-expression");
|
||||
const button_refcount = document.getElementById("button-refcount");
|
||||
const error_box = document.getElementById("error");
|
||||
|
||||
button_expression.onclick = runExpressionTest;
|
||||
button_refcount.onclick = runRefcountTest;
|
||||
file_input.onchange = function () {
|
||||
error_box.innerHTML = "";
|
||||
};
|
||||
|
||||
async function runExpressionTest() {
|
||||
const file = getFile();
|
||||
const instance = await compileFileToInstance(file);
|
||||
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $#UserApp_main_1
|
||||
instance.exports.test_wrapper();
|
||||
}
|
||||
|
||||
async function runRefcountTest() {
|
||||
const file = getFile();
|
||||
const instance = await compileFileToInstance(file);
|
||||
const MAX_ALLOCATIONS = 100;
|
||||
const refcount_vector_addr =
|
||||
instance.exports.init_refcount_test(MAX_ALLOCATIONS);
|
||||
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $#UserApp_main_1
|
||||
instance.exports.test_wrapper();
|
||||
|
||||
const words = new Uint32Array(instance.exports.memory.buffer);
|
||||
function deref(addr8) {
|
||||
return words[addr8 >> 2];
|
||||
}
|
||||
|
||||
const actual_len = deref(refcount_vector_addr);
|
||||
const rc_pointers = [];
|
||||
for (let i = 0; i < actual_len; i++) {
|
||||
const offset = (1 + i) << 2;
|
||||
const rc_ptr = deref(refcount_vector_addr + offset);
|
||||
rc_pointers.push(rc_ptr);
|
||||
}
|
||||
|
||||
const rc_encoded = rc_pointers.map((ptr) => ptr && deref(ptr));
|
||||
const rc_encoded_hex = rc_encoded.map((x) =>
|
||||
x ? x.toString(16) : "(deallocated)"
|
||||
);
|
||||
const rc_values = rc_encoded.map((x) => x && x - 0x80000000 + 1);
|
||||
|
||||
console.log({ rc_values, rc_encoded_hex });
|
||||
}
|
||||
|
||||
function getFile() {
|
||||
const { files } = file_input;
|
||||
if (!files.length) {
|
||||
const msg = "Select a file!";
|
||||
error_box.innerHTML = msg;
|
||||
throw new Error(msg);
|
||||
}
|
||||
return files[0];
|
||||
}
|
||||
|
||||
async function compileFileToInstance(file) {
|
||||
const buffer = await file.arrayBuffer();
|
||||
|
||||
const wasiLinkObject = {};
|
||||
const importObject = createFakeWasiImports(wasiLinkObject);
|
||||
const result = await WebAssembly.instantiate(buffer, importObject);
|
||||
|
||||
wasiLinkObject.memory8 = new Uint8Array(
|
||||
result.instance.exports.memory.buffer
|
||||
);
|
||||
wasiLinkObject.memory32 = new Uint32Array(
|
||||
result.instance.exports.memory.buffer
|
||||
);
|
||||
|
||||
return result.instance;
|
||||
}
|
||||
|
||||
// If you print to stdout (for example in the platform), it calls these WASI imports.
|
||||
// This implementation uses console.log
|
||||
function createFakeWasiImports(wasiLinkObject) {
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
// fd_close : (i32) -> i32
|
||||
// Close a file descriptor. Note: This is similar to close in POSIX.
|
||||
// https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_close.html
|
||||
function fd_close(fd) {
|
||||
console.warn(`fd_close: ${{ fd }}`);
|
||||
return 0; // error code
|
||||
}
|
||||
|
||||
// fd_fdstat_get : (i32, i32) -> i32
|
||||
// Get the attributes of a file descriptor.
|
||||
// https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_fdstat_get.html
|
||||
function fd_fdstat_get(fd, stat_mut_ptr) {
|
||||
/*
|
||||
Tell WASI that stdout is a tty (no seek or tell)
|
||||
|
||||
https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/isatty.c
|
||||
|
||||
*Not* a tty if:
|
||||
(statbuf.fs_filetype != __WASI_FILETYPE_CHARACTER_DEVICE ||
|
||||
(statbuf.fs_rights_base & (__WASI_RIGHTS_FD_SEEK | __WASI_RIGHTS_FD_TELL)) != 0)
|
||||
|
||||
So it's sufficient to set:
|
||||
.fs_filetype = __WASI_FILETYPE_CHARACTER_DEVICE
|
||||
.fs_rights_base = 0
|
||||
|
||||
https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/headers/public/wasi/api.h
|
||||
|
||||
typedef uint8_t __wasi_filetype_t;
|
||||
typedef uint16_t __wasi_fdflags_t;
|
||||
typedef uint64_t __wasi_rights_t;
|
||||
#define __WASI_FILETYPE_CHARACTER_DEVICE (UINT8_C(2))
|
||||
typedef struct __wasi_fdstat_t { // 24 bytes total
|
||||
__wasi_filetype_t fs_filetype; // 1 byte
|
||||
// 1 byte padding
|
||||
__wasi_fdflags_t fs_flags; // 2 bytes
|
||||
// 4 bytes padding
|
||||
__wasi_rights_t fs_rights_base; // 8 bytes
|
||||
__wasi_rights_t fs_rights_inheriting; // 8 bytes
|
||||
} __wasi_fdstat_t;
|
||||
*/
|
||||
// console.warn(`fd_fdstat_get: ${{ fd, stat_mut_ptr }}`);
|
||||
const WASI_FILETYPE_CHARACTER_DEVICE = 2;
|
||||
wasiLinkObject.memory8[stat_mut_ptr] = WASI_FILETYPE_CHARACTER_DEVICE;
|
||||
wasiLinkObject.memory8
|
||||
.slice(stat_mut_ptr + 1, stat_mut_ptr + 24)
|
||||
.fill(0);
|
||||
|
||||
return 0; // error code
|
||||
}
|
||||
|
||||
// fd_seek : (i32, i64, i32, i32) -> i32
|
||||
// Move the offset of a file descriptor. Note: This is similar to lseek in POSIX.
|
||||
// https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_seek.html
|
||||
function fd_seek(fd, offset, whence, newoffset_mut_ptr) {
|
||||
console.warn(`fd_seek: ${{ fd, offset, whence, newoffset_mut_ptr }}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// fd_write : (i32, i32, i32, i32) -> i32
|
||||
// Write to a file descriptor. Note: This is similar to `writev` in POSIX.
|
||||
// https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_write.html
|
||||
function fd_write(fd, iovs_ptr, iovs_len, nwritten_mut_ptr) {
|
||||
let string_buffer = "";
|
||||
let nwritten = 0;
|
||||
|
||||
for (let i = 0; i < iovs_len; i++) {
|
||||
const index32 = iovs_ptr >> 2;
|
||||
const base = wasiLinkObject.memory32[index32];
|
||||
const len = wasiLinkObject.memory32[index32 + 1];
|
||||
iovs_ptr += 8;
|
||||
|
||||
if (!len) continue;
|
||||
|
||||
nwritten += len;
|
||||
|
||||
// For some reason we often get negative-looking buffer lengths with junk data.
|
||||
// Just skip the console.log, but still increase nwritten or it will loop forever.
|
||||
// Dunno why this happens, but it's working fine for printf debugging ¯\_(ツ)_/¯
|
||||
if (len >> 31) {
|
||||
break;
|
||||
}
|
||||
|
||||
const buf = wasiLinkObject.memory8.slice(base, base + len);
|
||||
const chunk = decoder.decode(buf);
|
||||
string_buffer += chunk;
|
||||
}
|
||||
wasiLinkObject.memory32[nwritten_mut_ptr >> 2] = nwritten;
|
||||
if (string_buffer) {
|
||||
console.log(string_buffer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// proc_exit : (i32) -> nil
|
||||
function proc_exit(exit_code) {
|
||||
if (exit_code) {
|
||||
throw new Error(`Wasm exited with code ${exit_code}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Signatures from wasm_test_platform.o
|
||||
const sig2 = (i32) => {};
|
||||
const sig6 = (i32a, i32b) => 0;
|
||||
const sig7 = (i32a, i32b, i32c) => 0;
|
||||
const sig9 = (i32a, i64b, i32c) => 0;
|
||||
const sig10 = (i32a, i64b, i64c, i32d) => 0;
|
||||
const sig11 = (i32a, i64b, i64c) => 0;
|
||||
const sig12 = (i32a) => 0;
|
||||
const sig13 = (i32a, i64b) => 0;
|
||||
const sig14 = (i32a, i32b, i32c, i64d, i32e) => 0;
|
||||
const sig15 = (i32a, i32b, i32c, i32d) => 0;
|
||||
const sig16 = (i32a, i64b, i32c, i32d) => 0;
|
||||
const sig17 = (i32a, i32b, i32c, i32d, i32e) => 0;
|
||||
const sig18 = (i32a, i32b, i32c, i32d, i64e, i64f, i32g) => 0;
|
||||
const sig19 = (i32a, i32b, i32c, i32d, i32e, i32f, i32g) => 0;
|
||||
const sig20 = (i32a, i32b, i32c, i32d, i32e, i64f, i64g, i32h, i32i) =>
|
||||
0;
|
||||
const sig21 = (i32a, i32b, i32c, i32d, i32e, i32f) => 0;
|
||||
const sig22 = () => 0;
|
||||
|
||||
return {
|
||||
wasi_snapshot_preview1: {
|
||||
args_get: sig6,
|
||||
args_sizes_get: sig6,
|
||||
environ_get: sig6,
|
||||
environ_sizes_get: sig6,
|
||||
clock_res_get: sig6,
|
||||
clock_time_get: sig9,
|
||||
fd_advise: sig10,
|
||||
fd_allocate: sig11,
|
||||
fd_close,
|
||||
fd_datasync: sig12,
|
||||
fd_fdstat_get,
|
||||
fd_fdstat_set_flags: sig6,
|
||||
fd_fdstat_set_rights: sig11,
|
||||
fd_filestat_get: sig6,
|
||||
fd_filestat_set_size: sig13,
|
||||
fd_filestat_set_times: sig10,
|
||||
fd_pread: sig14,
|
||||
fd_prestat_get: sig6,
|
||||
fd_prestat_dir_name: sig7,
|
||||
fd_pwrite: sig14,
|
||||
fd_read: sig15,
|
||||
fd_readdir: sig14,
|
||||
fd_renumber: sig6,
|
||||
fd_seek,
|
||||
fd_sync: sig12,
|
||||
fd_tell: sig6,
|
||||
fd_write,
|
||||
path_create_directory: sig7,
|
||||
path_filestat_get: sig17,
|
||||
path_filestat_set_times: sig18,
|
||||
path_link: sig19,
|
||||
path_open: sig20,
|
||||
path_readlink: sig21,
|
||||
path_remove_directory: sig7,
|
||||
path_rename: sig21,
|
||||
path_symlink: sig17,
|
||||
path_unlink_file: sig7,
|
||||
poll_oneoff: sig15,
|
||||
proc_exit,
|
||||
proc_raise: sig12,
|
||||
sched_yield: sig22,
|
||||
random_get: sig6,
|
||||
sock_recv: sig21,
|
||||
sock_send: sig17,
|
||||
sock_shutdown: sig6,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -3,10 +3,11 @@ use roc_build::link::{link, LinkType};
|
|||
use roc_builtins::bitcode;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_region::all::LineInfo;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS;
|
||||
use roc_mono::ir::pretty_print_ir_symbols;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
|
@ -56,7 +57,7 @@ pub fn helper(
|
|||
&stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
8,
|
||||
roc_target::TargetInfo::default_x86_64(),
|
||||
builtin_defs_map,
|
||||
);
|
||||
|
||||
|
@ -75,7 +76,7 @@ pub fn helper(
|
|||
// while you're working on the dev backend!
|
||||
{
|
||||
// println!("=========== Procedures ==========");
|
||||
// if PRETTY_PRINT_IR_SYMBOLS {
|
||||
// if pretty_print_ir_symbols() {
|
||||
// println!("");
|
||||
// for proc in procedures.values() {
|
||||
// println!("{}", proc.to_pretty(200));
|
||||
|
@ -94,7 +95,7 @@ pub fn helper(
|
|||
// println!("=================================\n");
|
||||
}
|
||||
|
||||
debug_assert_eq!(exposed_to_host.len(), 1);
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
let main_fn_symbol = loaded.entry_point.symbol;
|
||||
let main_fn_layout = loaded.entry_point.layout;
|
||||
|
||||
|
@ -124,6 +125,7 @@ pub fn helper(
|
|||
continue;
|
||||
}
|
||||
|
||||
let line_info = LineInfo::new(&src);
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
|
@ -139,7 +141,7 @@ pub fn helper(
|
|||
continue;
|
||||
}
|
||||
_ => {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -150,7 +152,7 @@ pub fn helper(
|
|||
}
|
||||
|
||||
for problem in type_problems {
|
||||
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
|
||||
if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) {
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -160,7 +162,7 @@ pub fn helper(
|
|||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let report = mono_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -177,7 +179,7 @@ pub fn helper(
|
|||
let env = roc_gen_dev::Env {
|
||||
arena,
|
||||
module_id,
|
||||
exposed_to_host: exposed_to_host.keys().copied().collect(),
|
||||
exposed_to_host: exposed_to_host.values.keys().copied().collect(),
|
||||
lazy_literals,
|
||||
generate_allocators: true, // Needed for testing, since we don't have a platform
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ use roc_collections::all::{MutMap, MutSet};
|
|||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_types::subs::VarStore;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
|
@ -28,8 +29,6 @@ pub fn test_builtin_defs(symbol: Symbol, var_store: &mut VarStore) -> Option<Def
|
|||
builtin_defs_map(symbol, var_store)
|
||||
}
|
||||
|
||||
// this is not actually dead code, but only used by cfg_test modules
|
||||
// so "normally" it is dead, only at testing time is it used
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn create_llvm_module<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
|
@ -43,6 +42,8 @@ fn create_llvm_module<'a>(
|
|||
) -> (&'static str, String, &'a Module<'a>) {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
let target_info = roc_target::TargetInfo::from(target);
|
||||
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let src_dir = Path::new("fake/test/path");
|
||||
|
||||
|
@ -57,8 +58,6 @@ fn create_llvm_module<'a>(
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
|
||||
let exposed_types = MutMap::default();
|
||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
|
@ -67,7 +66,7 @@ fn create_llvm_module<'a>(
|
|||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
ptr_bytes,
|
||||
target_info,
|
||||
test_builtin_defs,
|
||||
);
|
||||
|
||||
|
@ -107,6 +106,7 @@ fn create_llvm_module<'a>(
|
|||
continue;
|
||||
}
|
||||
|
||||
let line_info = LineInfo::new(&src);
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
|
@ -123,7 +123,7 @@ fn create_llvm_module<'a>(
|
|||
| RuntimeError(_)
|
||||
| UnsupportedPattern(_, _)
|
||||
| ExposedButNotDefined(_) => {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -132,7 +132,7 @@ fn create_llvm_module<'a>(
|
|||
lines.push(buf);
|
||||
}
|
||||
_ => {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -143,7 +143,7 @@ fn create_llvm_module<'a>(
|
|||
}
|
||||
|
||||
for problem in type_problems {
|
||||
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
|
||||
if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) {
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -153,7 +153,7 @@ fn create_llvm_module<'a>(
|
|||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let report = mono_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -217,7 +217,7 @@ fn create_llvm_module<'a>(
|
|||
context,
|
||||
interns,
|
||||
module,
|
||||
ptr_bytes,
|
||||
target_info,
|
||||
is_gen_test,
|
||||
// important! we don't want any procedures to get the C calling convention
|
||||
exposed_to_host: MutSet::default(),
|
||||
|
@ -375,7 +375,7 @@ pub fn helper_wasm<'a>(
|
|||
|
||||
use std::process::Command;
|
||||
|
||||
Command::new("zig")
|
||||
Command::new(&crate::helpers::zig_executable())
|
||||
.current_dir(dir_path)
|
||||
.args(&[
|
||||
"wasm-ld",
|
||||
|
@ -593,10 +593,12 @@ macro_rules! assert_evals_to {
|
|||
assert_evals_to!($src, $expected, $ty, $crate::helpers::llvm::identity);
|
||||
}};
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
// Same as above, except with an additional transformation argument.
|
||||
// same as above, except with an additional transformation argument.
|
||||
{
|
||||
#[cfg(feature = "wasm-cli-run")]
|
||||
$crate::helpers::llvm::assert_wasm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
$crate::helpers::llvm::assert_wasm_evals_to!(
|
||||
$src, $expected, $ty, $transform, false, false
|
||||
);
|
||||
|
||||
$crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
}
|
||||
|
@ -641,6 +643,25 @@ macro_rules! assert_expect_failed {
|
|||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
$crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
};
|
||||
macro_rules! expect_runtime_error_panic {
|
||||
($src:expr) => {{
|
||||
#[cfg(feature = "wasm-cli-run")]
|
||||
$crate::helpers::llvm::assert_wasm_evals_to!(
|
||||
$src,
|
||||
false, // fake value/type for eval
|
||||
bool,
|
||||
$crate::helpers::llvm::identity,
|
||||
true // ignore problems
|
||||
);
|
||||
|
||||
$crate::helpers::llvm::assert_llvm_evals_to!(
|
||||
$src,
|
||||
false, // fake value/type for eval
|
||||
bool,
|
||||
$crate::helpers::llvm::identity,
|
||||
true // ignore problems
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -661,7 +682,7 @@ macro_rules! assert_non_opt_evals_to {
|
|||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
// Same as above, except with an additional transformation argument.
|
||||
{
|
||||
$crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
$crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform);
|
||||
}
|
||||
};
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {{
|
||||
|
@ -679,3 +700,5 @@ pub(crate) use assert_llvm_evals_to;
|
|||
pub(crate) use assert_non_opt_evals_to;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_wasm_evals_to;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use expect_runtime_error_panic;
|
||||
|
|
|
@ -10,6 +10,14 @@ pub mod wasm;
|
|||
#[cfg(feature = "gen-wasm")]
|
||||
pub mod wasm32_test_result;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn zig_executable() -> String {
|
||||
match std::env::var("ROC_ZIG") {
|
||||
Ok(path) => path,
|
||||
Err(_) => "zig".into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in the with_larger_debug_stack() function, for tests that otherwise
|
||||
/// run out of stack space in debug builds (but don't in --release builds)
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
use core::cell::Cell;
|
||||
use roc_gen_wasm::wasm_module::{Export, ExportType};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use wasmer::Memory;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Path, PathBuf};
|
||||
use wasmer::{Memory, WasmPtr};
|
||||
|
||||
use crate::helpers::from_wasm32_memory::FromWasm32Memory;
|
||||
use crate::helpers::wasm32_test_result::Wasm32TestResult;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_gen_wasm::MEMORY_NAME;
|
||||
use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME};
|
||||
|
||||
// Should manually match build.rs
|
||||
const PLATFORM_FILENAME: &str = "wasm_test_platform";
|
||||
|
||||
const TEST_OUT_DIR: &str = env!("TEST_GEN_OUT");
|
||||
const LIBC_A_FILE: &str = env!("TEST_GEN_WASM_LIBC_PATH");
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS;
|
||||
const OUT_DIR_VAR: &str = "TEST_GEN_OUT";
|
||||
|
||||
const TEST_WRAPPER_NAME: &str = "test_wrapper";
|
||||
const INIT_REFCOUNT_NAME: &str = "init_refcount_test";
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
@ -34,15 +32,53 @@ fn promote_expr_to_module(src: &str) -> String {
|
|||
buffer
|
||||
}
|
||||
|
||||
pub enum TestType {
|
||||
/// Test that some Roc code evaluates to the right result
|
||||
Evaluate,
|
||||
/// Test that some Roc values have the right refcount
|
||||
Refcount,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
||||
pub fn compile_and_load<'a, T: Wasm32TestResult>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
src: &str,
|
||||
stdlib: &'a roc_builtins::std::StdLib,
|
||||
_result_type_dummy: &T,
|
||||
_test_wrapper_type_info: PhantomData<T>,
|
||||
_test_type: TestType,
|
||||
) -> wasmer::Instance {
|
||||
use std::path::{Path, PathBuf};
|
||||
let platform_bytes = load_platform_and_builtins();
|
||||
|
||||
let compiled_bytes =
|
||||
compile_roc_to_wasm_bytes(arena, stdlib, &platform_bytes, src, _test_wrapper_type_info);
|
||||
|
||||
if DEBUG_LOG_SETTINGS.keep_test_binary {
|
||||
let build_dir_hash = src_hash(src);
|
||||
save_wasm_file(&compiled_bytes, build_dir_hash)
|
||||
};
|
||||
|
||||
load_bytes_into_runtime(compiled_bytes)
|
||||
}
|
||||
|
||||
fn load_platform_and_builtins() -> std::vec::Vec<u8> {
|
||||
let out_dir = std::env::var(OUT_DIR_VAR).unwrap();
|
||||
let platform_path = Path::new(&out_dir).join([PLATFORM_FILENAME, "o"].join("."));
|
||||
std::fs::read(&platform_path).unwrap()
|
||||
}
|
||||
|
||||
fn src_hash(src: &str) -> u64 {
|
||||
let mut hash_state = DefaultHasher::new();
|
||||
src.hash(&mut hash_state);
|
||||
hash_state.finish()
|
||||
}
|
||||
|
||||
fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
stdlib: &'a roc_builtins::std::StdLib,
|
||||
preload_bytes: &[u8],
|
||||
src: &str,
|
||||
_test_wrapper_type_info: PhantomData<T>,
|
||||
) -> Vec<u8> {
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let src_dir = Path::new("fake/test/path");
|
||||
|
||||
|
@ -58,7 +94,6 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
}
|
||||
|
||||
let exposed_types = MutMap::default();
|
||||
let ptr_bytes = 4;
|
||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
|
@ -66,7 +101,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
ptr_bytes,
|
||||
roc_target::TargetInfo::default_wasm32(),
|
||||
builtin_defs_map,
|
||||
);
|
||||
|
||||
|
@ -81,32 +116,13 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
..
|
||||
} = loaded;
|
||||
|
||||
// You can comment and uncomment this block out to get more useful information
|
||||
// while you're working on the wasm backend!
|
||||
{
|
||||
// println!("=========== Procedures ==========");
|
||||
// if PRETTY_PRINT_IR_SYMBOLS {
|
||||
// println!("");
|
||||
// for proc in procedures.values() {
|
||||
// println!("{}", proc.to_pretty(200));
|
||||
// }
|
||||
// } else {
|
||||
// println!("{:?}", procedures.values());
|
||||
// }
|
||||
// println!("=================================\n");
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
|
||||
// println!("=========== Interns ==========");
|
||||
// println!("{:?}", interns);
|
||||
// println!("=================================\n");
|
||||
|
||||
// println!("=========== Exposed ==========");
|
||||
// println!("{:?}", exposed_to_host);
|
||||
// println!("=================================\n");
|
||||
}
|
||||
|
||||
debug_assert_eq!(exposed_to_host.len(), 1);
|
||||
|
||||
let exposed_to_host = exposed_to_host.keys().copied().collect::<MutSet<_>>();
|
||||
let exposed_to_host = exposed_to_host
|
||||
.values
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<MutSet<_>>();
|
||||
|
||||
let env = roc_gen_wasm::Env {
|
||||
arena,
|
||||
|
@ -114,94 +130,55 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
exposed_to_host,
|
||||
};
|
||||
|
||||
let (mut wasm_module, main_fn_index) =
|
||||
roc_gen_wasm::build_module_help(&env, &mut interns, procedures).unwrap();
|
||||
let (mut module, called_preload_fns, main_fn_index) =
|
||||
roc_gen_wasm::build_module_without_test_wrapper(
|
||||
&env,
|
||||
&mut interns,
|
||||
preload_bytes,
|
||||
procedures,
|
||||
);
|
||||
|
||||
T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index);
|
||||
T::insert_test_wrapper(arena, &mut module, TEST_WRAPPER_NAME, main_fn_index);
|
||||
|
||||
let mut module_bytes = std::vec::Vec::with_capacity(4096);
|
||||
wasm_module.serialize_mut(&mut module_bytes);
|
||||
// Export the initialiser function for refcount tests
|
||||
let init_refcount_bytes = INIT_REFCOUNT_NAME.as_bytes();
|
||||
let init_refcount_idx = module.names.functions[init_refcount_bytes];
|
||||
module.export.append(Export {
|
||||
name: arena.alloc_slice_copy(init_refcount_bytes),
|
||||
ty: ExportType::Func,
|
||||
index: init_refcount_idx,
|
||||
});
|
||||
|
||||
// now, do wasmer stuff
|
||||
module.remove_dead_preloads(env.arena, called_preload_fns);
|
||||
|
||||
use wasmer::{Instance, Module, Store};
|
||||
let mut app_module_bytes = std::vec::Vec::with_capacity(module.size());
|
||||
module.serialize(&mut app_module_bytes);
|
||||
|
||||
app_module_bytes
|
||||
}
|
||||
|
||||
fn save_wasm_file(app_module_bytes: &[u8], build_dir_hash: u64) {
|
||||
let debug_dir_str = format!("/tmp/roc/gen_wasm/{:016x}", build_dir_hash);
|
||||
let debug_dir_path = Path::new(&debug_dir_str);
|
||||
let final_wasm_file = debug_dir_path.join("final.wasm");
|
||||
|
||||
std::fs::create_dir_all(debug_dir_path).unwrap();
|
||||
std::fs::write(&final_wasm_file, app_module_bytes).unwrap();
|
||||
|
||||
println!(
|
||||
"Debug command:\n\twasm-objdump -dx {}",
|
||||
final_wasm_file.to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
fn load_bytes_into_runtime(bytes: Vec<u8>) -> wasmer::Instance {
|
||||
use wasmer::{Module, Store};
|
||||
use wasmer_wasi::WasiState;
|
||||
|
||||
let store = Store::default();
|
||||
|
||||
// Keep the final .wasm file for debugging with wasm-objdump or wasm2wat
|
||||
const DEBUG_WASM_FILE: bool = false;
|
||||
|
||||
let wasmer_module = {
|
||||
let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped
|
||||
let debug_dir: String; // persistent directory for debugging
|
||||
|
||||
let wasm_build_dir: &Path = if DEBUG_WASM_FILE {
|
||||
// Directory name based on a hash of the Roc source
|
||||
let mut hash_state = DefaultHasher::new();
|
||||
src.hash(&mut hash_state);
|
||||
let src_hash = hash_state.finish();
|
||||
debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash);
|
||||
std::fs::create_dir_all(&debug_dir).unwrap();
|
||||
println!(
|
||||
"Debug command:\n\twasm-objdump -sdx {}/final.wasm",
|
||||
&debug_dir
|
||||
);
|
||||
Path::new(&debug_dir)
|
||||
} else {
|
||||
tmp_dir = tempdir().unwrap();
|
||||
tmp_dir.path()
|
||||
};
|
||||
|
||||
let final_wasm_file = wasm_build_dir.join("final.wasm");
|
||||
let app_o_file = wasm_build_dir.join("app.o");
|
||||
let test_platform_o = format!("{}/{}.o", TEST_OUT_DIR, PLATFORM_FILENAME);
|
||||
|
||||
// write the module to a file so the linker can access it
|
||||
std::fs::write(&app_o_file, &module_bytes).unwrap();
|
||||
|
||||
let args = &[
|
||||
"wasm-ld",
|
||||
// input files
|
||||
app_o_file.to_str().unwrap(),
|
||||
bitcode::BUILTINS_WASM32_OBJ_PATH,
|
||||
&test_platform_o,
|
||||
LIBC_A_FILE,
|
||||
// output
|
||||
"-o",
|
||||
final_wasm_file.to_str().unwrap(),
|
||||
// we don't define `_start`
|
||||
"--no-entry",
|
||||
// If you only specify test_wrapper, it will stop at the call to UserApp_main_1
|
||||
// But if you specify both exports, you get all the dependencies.
|
||||
//
|
||||
// It seems that it will not write out an export you didn't explicitly specify,
|
||||
// even if it's a dependency of another export!
|
||||
// In our case we always export main and test_wrapper so that's OK.
|
||||
"--export",
|
||||
"test_wrapper",
|
||||
"--export",
|
||||
"#UserApp_main_1",
|
||||
];
|
||||
|
||||
let linker_output = std::process::Command::new("zig")
|
||||
.args(args)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
if !linker_output.status.success() {
|
||||
print!("\nLINKER FAILED\n");
|
||||
for arg in args {
|
||||
print!("{} ", arg);
|
||||
}
|
||||
println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap());
|
||||
println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap());
|
||||
}
|
||||
|
||||
Module::from_file(&store, &final_wasm_file).unwrap()
|
||||
};
|
||||
let wasmer_module = Module::new(&store, &bytes).unwrap();
|
||||
|
||||
// First, we create the `WasiEnv`
|
||||
use wasmer_wasi::WasiState;
|
||||
let mut wasi_env = WasiState::new("hello").finalize().unwrap();
|
||||
|
||||
// Then, we get the import object related to our WASI
|
||||
|
@ -210,11 +187,11 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
.import_object(&wasmer_module)
|
||||
.unwrap_or_else(|_| wasmer::imports!());
|
||||
|
||||
Instance::new(&wasmer_module, &import_object).unwrap()
|
||||
wasmer::Instance::new(&wasmer_module, &import_object).unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn assert_wasm_evals_to_help<T>(src: &str, phantom: T) -> Result<T, String>
|
||||
pub fn assert_wasm_evals_to_help<T>(src: &str, phantom: PhantomData<T>) -> Result<T, String>
|
||||
where
|
||||
T: FromWasm32Memory + Wasm32TestResult,
|
||||
{
|
||||
|
@ -223,7 +200,8 @@ where
|
|||
// NOTE the stdlib must be in the arena; just taking a reference will segfault
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
||||
let instance = crate::helpers::wasm::helper_wasm(&arena, src, stdlib, &phantom);
|
||||
let instance =
|
||||
crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, TestType::Evaluate);
|
||||
|
||||
let memory = instance.exports.get_memory(MEMORY_NAME).unwrap();
|
||||
|
||||
|
@ -238,9 +216,15 @@ where
|
|||
};
|
||||
|
||||
if false {
|
||||
println!("test_wrapper returned 0x{:x}", address);
|
||||
println!("Stack:");
|
||||
crate::helpers::wasm::debug_memory_hex(memory, address, std::mem::size_of::<T>());
|
||||
}
|
||||
|
||||
if false {
|
||||
println!("Heap:");
|
||||
// Manually provide address and size based on printf in wasm_test_platform.c
|
||||
crate::helpers::wasm::debug_memory_hex(memory, 0x11440, 24);
|
||||
}
|
||||
let output = <T as FromWasm32Memory>::decode(memory, address as u32);
|
||||
|
||||
Ok(output)
|
||||
|
@ -248,6 +232,73 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn assert_wasm_refcounts_help<T>(
|
||||
src: &str,
|
||||
phantom: PhantomData<T>,
|
||||
num_refcounts: usize,
|
||||
) -> Result<Vec<u32>, String>
|
||||
where
|
||||
T: FromWasm32Memory + Wasm32TestResult,
|
||||
{
|
||||
let arena = bumpalo::Bump::new();
|
||||
|
||||
// NOTE the stdlib must be in the arena; just taking a reference will segfault
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
||||
let instance =
|
||||
crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, TestType::Refcount);
|
||||
|
||||
let memory = instance.exports.get_memory(MEMORY_NAME).unwrap();
|
||||
|
||||
let expected_len = num_refcounts as i32;
|
||||
let init_refcount_test = instance.exports.get_function(INIT_REFCOUNT_NAME).unwrap();
|
||||
let init_result = init_refcount_test.call(&[wasmer::Value::I32(expected_len)]);
|
||||
let refcount_vector_addr = match init_result {
|
||||
Err(e) => return Err(format!("{:?}", e)),
|
||||
Ok(result) => match result[0] {
|
||||
wasmer::Value::I32(a) => a,
|
||||
_ => panic!(),
|
||||
},
|
||||
};
|
||||
|
||||
// Run the test
|
||||
let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap();
|
||||
match test_wrapper.call(&[]) {
|
||||
Err(e) => return Err(format!("{:?}", e)),
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
||||
// Check we got the right number of refcounts
|
||||
let refcount_vector_len: WasmPtr<i32> = WasmPtr::new(refcount_vector_addr as u32);
|
||||
let actual_len = refcount_vector_len.deref(memory).unwrap().get();
|
||||
if actual_len != expected_len {
|
||||
panic!("Expected {} refcounts but got {}", expected_len, actual_len);
|
||||
}
|
||||
|
||||
// Read the actual refcount values
|
||||
let refcount_ptr_array: WasmPtr<WasmPtr<i32>, wasmer::Array> =
|
||||
WasmPtr::new(4 + refcount_vector_addr as u32);
|
||||
let refcount_ptrs: &[Cell<WasmPtr<i32>>] = refcount_ptr_array
|
||||
.deref(memory, 0, num_refcounts as u32)
|
||||
.unwrap();
|
||||
|
||||
let mut refcounts = Vec::with_capacity(num_refcounts);
|
||||
for i in 0..num_refcounts {
|
||||
let rc_ptr = refcount_ptrs[i].get();
|
||||
let rc = if rc_ptr.offset() == 0 {
|
||||
// RC pointer has been set to null, which means the value has been freed.
|
||||
// In tests, we simply represent this as zero refcount.
|
||||
0
|
||||
} else {
|
||||
let rc_encoded = rc_ptr.deref(memory).unwrap().get();
|
||||
(rc_encoded - i32::MIN + 1) as u32
|
||||
};
|
||||
refcounts.push(rc);
|
||||
}
|
||||
Ok(refcounts)
|
||||
}
|
||||
|
||||
/// Print out hex bytes of the test result, and a few words on either side
|
||||
/// Can be handy for debugging misalignment issues etc.
|
||||
pub fn debug_memory_hex(memory: &Memory, address: i32, size: usize) {
|
||||
|
@ -257,12 +308,17 @@ pub fn debug_memory_hex(memory: &Memory, address: i32, size: usize) {
|
|||
};
|
||||
|
||||
let extra_words = 2;
|
||||
let offset = (address as usize) / 4;
|
||||
let start = offset - extra_words;
|
||||
let end = offset + (size / 4) + extra_words;
|
||||
let result_start = (address as usize) / 4;
|
||||
let result_end = result_start + ((size + 3) / 4);
|
||||
let start = result_start - extra_words;
|
||||
let end = result_end + extra_words;
|
||||
|
||||
for index in start..end {
|
||||
let result_marker = if index == offset { "*" } else { " " };
|
||||
let result_marker = if index >= result_start && index < result_end {
|
||||
"|"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
println!(
|
||||
"{:x} {} {:08x}",
|
||||
index * 4,
|
||||
|
@ -270,12 +326,13 @@ pub fn debug_memory_hex(memory: &Memory, address: i32, size: usize) {
|
|||
memory_words[index]
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! assert_wasm_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
let phantom = <$ty>::default();
|
||||
let phantom = std::marker::PhantomData;
|
||||
match $crate::helpers::wasm::assert_wasm_evals_to_help::<$ty>($src, phantom) {
|
||||
Err(msg) => panic!("{:?}", msg),
|
||||
Ok(actual) => {
|
||||
|
@ -316,7 +373,29 @@ pub fn identity<T>(value: T) -> T {
|
|||
value
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! assert_refcounts {
|
||||
// We need the result type to generate the test_wrapper, even though we ignore the value!
|
||||
// We can't just call `main` with no args, because some tests return structs, via pointer arg!
|
||||
// Also we need to know how much stack space to reserve for the struct.
|
||||
($src: expr, $ty: ty, $expected_refcounts: expr) => {{
|
||||
let phantom = std::marker::PhantomData;
|
||||
let num_refcounts = $expected_refcounts.len();
|
||||
let result =
|
||||
$crate::helpers::wasm::assert_wasm_refcounts_help::<$ty>($src, phantom, num_refcounts);
|
||||
match result {
|
||||
Err(msg) => panic!("{:?}", msg),
|
||||
Ok(actual_refcounts) => {
|
||||
assert_eq!(&actual_refcounts, $expected_refcounts)
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_evals_to;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_wasm_evals_to;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_refcounts;
|
||||
|
|
|
@ -14,25 +14,27 @@ pub trait Wasm32TestResult {
|
|||
wrapper_name: &str,
|
||||
main_function_index: u32,
|
||||
) {
|
||||
let index = module.code.code_builders.len() as u32;
|
||||
let index = module.import.function_count
|
||||
+ module.code.preloaded_count
|
||||
+ module.code.code_builders.len() as u32;
|
||||
|
||||
module.add_function_signature(Signature {
|
||||
param_types: Vec::with_capacity_in(0, arena),
|
||||
ret_type: Some(ValueType::I32),
|
||||
});
|
||||
|
||||
module.export.entries.push(Export {
|
||||
name: wrapper_name.to_string(),
|
||||
module.export.append(Export {
|
||||
name: arena.alloc_slice_copy(wrapper_name.as_bytes()),
|
||||
ty: ExportType::Func,
|
||||
index,
|
||||
});
|
||||
|
||||
let symbol_table = module.linking.symbol_table_mut();
|
||||
symbol_table.push(SymInfo::Function(WasmObjectSymbol::Defined {
|
||||
let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined {
|
||||
flags: 0,
|
||||
index,
|
||||
name: wrapper_name.to_string(),
|
||||
}));
|
||||
});
|
||||
module.linking.symbol_table.push(linker_symbol);
|
||||
|
||||
let mut code_builder = CodeBuilder::new(arena);
|
||||
Self::build_wrapper_body(&mut code_builder, main_function_index);
|
||||
|
@ -57,7 +59,7 @@ macro_rules! build_wrapper_body_primitive {
|
|||
code_builder.$store_instruction($align, 0);
|
||||
code_builder.get_local(frame_pointer_id);
|
||||
|
||||
code_builder.build_fn_header(local_types, frame_size, frame_pointer);
|
||||
code_builder.build_fn_header_and_footer(local_types, frame_size, frame_pointer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -84,7 +86,7 @@ fn build_wrapper_body_stack_memory(
|
|||
code_builder.get_local(local_id);
|
||||
code_builder.call(main_function_index, main_symbol_index, 0, true);
|
||||
code_builder.get_local(local_id);
|
||||
code_builder.build_fn_header(local_types, size as i32, frame_pointer);
|
||||
code_builder.build_fn_header_and_footer(local_types, size as i32, frame_pointer);
|
||||
}
|
||||
|
||||
macro_rules! wasm_test_result_stack_memory {
|
||||
|
@ -114,7 +116,7 @@ wasm_test_result_primitive!(u64, i64_store, Align::Bytes8);
|
|||
wasm_test_result_primitive!(i64, i64_store, Align::Bytes8);
|
||||
wasm_test_result_primitive!(usize, i32_store, Align::Bytes4);
|
||||
|
||||
wasm_test_result_primitive!(f32, f32_store, Align::Bytes8);
|
||||
wasm_test_result_primitive!(f32, f32_store, Align::Bytes4);
|
||||
wasm_test_result_primitive!(f64, f64_store, Align::Bytes8);
|
||||
|
||||
wasm_test_result_stack_memory!(u128);
|
||||
|
@ -147,7 +149,7 @@ impl Wasm32TestResult for () {
|
|||
let main_symbol_index = main_function_index;
|
||||
code_builder.call(main_function_index, main_symbol_index, 0, false);
|
||||
code_builder.get_global(0);
|
||||
code_builder.build_fn_header(&[], 0, None);
|
||||
code_builder.build_fn_header_and_footer(&[], 0, None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,146 @@
|
|||
#include <stdio.h>
|
||||
|
||||
// If any printf is included for compilation, even if unused, test runs take 50% longer
|
||||
#define DEBUG 0
|
||||
// Makes test runs take 50% longer, due to linking
|
||||
#define ENABLE_PRINTF 0
|
||||
|
||||
void *roc_alloc(size_t size, unsigned int alignment) { return malloc(size); }
|
||||
typedef struct
|
||||
{
|
||||
size_t length;
|
||||
size_t *elements[]; // flexible array member
|
||||
} Vector;
|
||||
|
||||
// Globals for refcount testing
|
||||
Vector *rc_pointers;
|
||||
size_t rc_pointers_capacity;
|
||||
|
||||
// The rust test passes us the max number of allocations it expects to make,
|
||||
// and we tell it where we're going to write the refcount pointers.
|
||||
// It won't actually read that memory until later, when the test is done.
|
||||
Vector *init_refcount_test(size_t capacity)
|
||||
{
|
||||
rc_pointers_capacity = capacity;
|
||||
|
||||
rc_pointers = malloc((1 + capacity) * sizeof(size_t *));
|
||||
rc_pointers->length = 0;
|
||||
for (size_t i = 0; i < capacity; ++i)
|
||||
rc_pointers->elements[i] = NULL;
|
||||
|
||||
return rc_pointers;
|
||||
}
|
||||
|
||||
#if ENABLE_PRINTF
|
||||
#define ASSERT(condition, format, ...) \
|
||||
if (!(condition)) \
|
||||
{ \
|
||||
printf("ASSERT FAILED: " #format "\n", __VA_ARGS__); \
|
||||
abort(); \
|
||||
}
|
||||
#else
|
||||
#define ASSERT(condition, format, ...) \
|
||||
if (!(condition)) \
|
||||
abort();
|
||||
#endif
|
||||
|
||||
size_t *alloc_ptr_to_rc_ptr(void *ptr, unsigned int alignment)
|
||||
{
|
||||
size_t alloc_addr = (size_t)ptr;
|
||||
size_t rc_addr = alloc_addr + alignment - sizeof(size_t);
|
||||
return (size_t *)rc_addr;
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
|
||||
void *roc_alloc(size_t size, unsigned int alignment)
|
||||
{
|
||||
void *allocated = malloc(size);
|
||||
|
||||
if (rc_pointers)
|
||||
{
|
||||
ASSERT(alignment >= sizeof(size_t), "alignment %zd != %zd", alignment, sizeof(size_t));
|
||||
size_t num_alloc = rc_pointers->length + 1;
|
||||
ASSERT(num_alloc <= rc_pointers_capacity, "Too many allocations %zd > %zd", num_alloc, rc_pointers_capacity);
|
||||
|
||||
size_t *rc_ptr = alloc_ptr_to_rc_ptr(allocated, alignment);
|
||||
rc_pointers->elements[rc_pointers->length] = rc_ptr;
|
||||
rc_pointers->length++;
|
||||
}
|
||||
|
||||
#if ENABLE_PRINTF
|
||||
if (!allocated)
|
||||
{
|
||||
fprintf(stderr, "roc_alloc failed\n");
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("roc_alloc allocated %d bytes with alignment %d at %p\n", size, alignment, allocated);
|
||||
}
|
||||
#endif
|
||||
return allocated;
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
|
||||
void *roc_realloc(void *ptr, size_t new_size, size_t old_size,
|
||||
unsigned int alignment)
|
||||
{
|
||||
#if ENABLE_PRINTF
|
||||
printf("roc_realloc reallocated %p from %d to %d with alignment %zd\n",
|
||||
ptr, old_size, new_size, alignment);
|
||||
#endif
|
||||
return realloc(ptr, new_size);
|
||||
}
|
||||
|
||||
void roc_dealloc(void *ptr, unsigned int alignment) { free(ptr); }
|
||||
//--------------------------
|
||||
|
||||
void roc_dealloc(void *ptr, unsigned int alignment)
|
||||
{
|
||||
if (rc_pointers)
|
||||
{
|
||||
// Null out the entry in the test array to indicate that it was freed
|
||||
// Then even if malloc reuses the space, everything still works
|
||||
size_t *rc_ptr = alloc_ptr_to_rc_ptr(ptr, alignment);
|
||||
int i = 0;
|
||||
for (; i < rc_pointers->length; ++i)
|
||||
{
|
||||
if (rc_pointers->elements[i] == rc_ptr)
|
||||
{
|
||||
rc_pointers->elements[i] = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int was_found = i < rc_pointers->length;
|
||||
ASSERT(was_found, "RC pointer not found %p", rc_ptr);
|
||||
}
|
||||
|
||||
#if ENABLE_PRINTF
|
||||
printf("roc_dealloc deallocated %p with alignment %zd\n", ptr, alignment);
|
||||
#endif
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
|
||||
void roc_panic(void *ptr, unsigned int alignment)
|
||||
{
|
||||
#if DEBUG
|
||||
#if ENABLE_PRINTF
|
||||
char *msg = (char *)ptr;
|
||||
fprintf(stderr,
|
||||
"Application crashed with message\n\n %s\n\nShutting down\n", msg);
|
||||
#endif
|
||||
exit(1);
|
||||
abort();
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
|
||||
void *roc_memcpy(void *dest, const void *src, size_t n)
|
||||
{
|
||||
return memcpy(dest, src, n);
|
||||
}
|
||||
|
||||
void *roc_memset(void *str, int c, size_t n) { return memset(str, c, n); }
|
||||
//--------------------------
|
||||
|
||||
void *roc_memset(void *str, int c, size_t n)
|
||||
{
|
||||
return memset(str, c, n);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue