Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

report roc_panic to the user in the web repl #5921

Merged
merged 3 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -52,8 +52,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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bitwise op should work fine. It can't be a negative number because we read it out of an unsigned array. 👍


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