mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
moved all crates into seperate folder + related path fixes
This commit is contained in:
parent
12ef03bb86
commit
eee85fa45d
1063 changed files with 92 additions and 93 deletions
372
crates/compiler/test_gen/src/helpers/debug-wasm-test.html
Normal file
372
crates/compiler/test_gen/src/helpers/debug-wasm-test.html
Normal file
|
@ -0,0 +1,372 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #ccc;
|
||||
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: #aaa;
|
||||
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;
|
||||
const STDOUT = 1;
|
||||
|
||||
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) {
|
||||
if (fd === STDOUT) {
|
||||
console.log(string_buffer);
|
||||
} else {
|
||||
console.error(string_buffer);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// proc_exit : (i32) -> nil
|
||||
function proc_exit(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>
|
Loading…
Add table
Add a link
Reference in a new issue