Skip to content

Commit

Permalink
Merge pull request #5921 from roc-lang/wasm-repl-crash
Browse files Browse the repository at this point in the history
report roc_panic to the user in the web repl
  • Loading branch information
brian-carroll authored Oct 25, 2023
2 parents bacdfa3 + f1cdbb4 commit c509252
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 11 deletions.
10 changes: 5 additions & 5 deletions crates/compiler/test_gen/src/helpers/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,12 @@ impl<'a> ImportDispatcher for TestDispatcher<'a> {
self.wasi.dispatch(function_name, arguments, memory)
} else if module_name == "env" && function_name == "send_panic_msg_to_rust" {
let msg_ptr = arguments[0].expect_i32().unwrap();
let tag = arguments[1].expect_i32().unwrap();
let panic_tag = arguments[1].expect_i32().unwrap();
let roc_msg = RocStr::decode(memory, msg_ptr as _);
let msg = match tag {
0 => format!(r#"Roc failed with message: "{}""#, roc_msg),
1 => format!(r#"User crash with message: "{}""#, roc_msg),
tag => format!(r#"Got an invald panic tag: "{}""#, tag),
let msg = match panic_tag {
0 => format!(r#"Roc failed with message: "{roc_msg}""#),
1 => format!(r#"User crash with message: "{roc_msg}""#),
tag => format!(r#"Got an invald panic tag: "{panic_tag}""#),
};
panic!("{}", msg)
} else {
Expand Down
6 changes: 3 additions & 3 deletions crates/compiler/test_gen/src/helpers/wasm_test_platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ void roc_dealloc(void *ptr, unsigned int alignment)

//--------------------------

extern void send_panic_msg_to_rust(void* msg, uint32_t tag_id);
extern void send_panic_msg_to_rust(void* msg, uint32_t panic_tag);

void roc_panic(void* msg, unsigned int tag_id)
void roc_panic(void* msg, unsigned int panic_tag)
{
send_panic_msg_to_rust(msg, tag_id);
send_panic_msg_to_rust(msg, panic_tag);
exit(101);
}

Expand Down
5 changes: 4 additions & 1 deletion crates/repl_wasm/src/repl_platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,11 @@ void roc_dealloc(void *ptr, unsigned int alignment)

//--------------------------

void roc_panic(void *ptr, unsigned int alignment)
extern void send_panic_msg_to_js(void *ptr, unsigned int panic_tag);

void roc_panic(void *ptr, unsigned int panic_tag)
{
send_panic_msg_to_js(ptr, panic_tag);
#if ENABLE_PRINTF
char *msg = (char *)ptr;
fprintf(stderr,
Expand Down
72 changes: 70 additions & 2 deletions www/public/repl/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,51 @@ async function processInputQueue() {
// Callbacks to JS from Rust
// ----------------------------------------------------------------------------

var ROC_PANIC_INFO = null;

function send_panic_msg_to_js(rocstr_ptr, panic_tag) {
const { memory } = repl.app.exports;

const rocStrBytes = new Int8Array(memory.buffer, rocstr_ptr, 12);
const finalByte = rocStrBytes[11]

let stringBytes = "";
if (finalByte < 0) {
// small string

// bitwise ops on negative JS numbers are weird. This clears the bit that we
// use to indicate a small string. In rust it's `finalByte as u8 ^ 0b1000_0000`
const length = finalByte + 128;
stringBytes = new Uint8Array(memory.buffer, rocstr_ptr, length);
} else {
// big string
const rocStrWords = new Uint32Array(memory.buffer, rocstr_ptr, 3);
const [ptr, len, _cap] = rocStrWords;

const SEAMLESS_SLICE_BIT = 1 << 31;
const length = len & (~SEAMLESS_SLICE_BIT);

stringBytes = new Uint8Array(memory.buffer, ptr, length);
}

const decodedString = repl.textDecoder.decode(stringBytes);

ROC_PANIC_INFO = {
msg: decodedString,
panic_tag: panic_tag,
};
}

// Load Wasm code into the browser's virtual machine, so we can run it later.
// This operation is async, so we call it before entering any code shared
// with the command-line REPL, which is sync.
async function js_create_app(wasm_module_bytes) {
const { instance } = await WebAssembly.instantiate(wasm_module_bytes);
const { instance } = await WebAssembly.instantiate(wasm_module_bytes, {
env: {
send_panic_msg_to_js: send_panic_msg_to_js,
}
});

// Keep the instance alive so we can run it later from shared REPL code
repl.app = instance;
}
Expand All @@ -180,13 +220,41 @@ function js_run_app() {

// Run the user code, and remember the result address
// We'll pass it to Rust in the next callback
repl.result_addr = wrapper();
try {
repl.result_addr = wrapper();
} catch (e) {
// an exception could be that roc_panic was invoked,
// or some other crash (likely a compiler bug)
if (ROC_PANIC_INFO === null) {
throw e;
} else {
// when roc_panic set an error message, display it
const { msg, panic_tag } = ROC_PANIC_INFO;
ROC_PANIC_INFO = null;

console.error(format_roc_panic_message(msg, panic_tag));
}
}

// Tell Rust how much space to reserve for its copy of the app's memory buffer.
// We couldn't know that size until we actually ran the app.
return memory.buffer.byteLength;
}

function format_roc_panic_message(msg, panic_tag) {
switch (panic_tag) {
case 0: {
return `Roc failed with message: "${msg}"`;
}
case 1: {
return `User crash with message: "${msg}"`;
}
default: {
return `Got an invalid panic tag: "${panic_tag}"`;
}
}
}

// After Rust has allocated space for the app's memory buffer,
// we copy it, and return the result address too
function js_get_result_and_memory(buffer_alloc_addr) {
Expand Down

0 comments on commit c509252

Please sign in to comment.