Rust + Emscripten leading to an alignment error

I have been trying to get one of my projects working with Emscripten and I've almost got everything up and running but I'm getting an alignment error in the browser and I have no clue what's causing it.

The actual code is right here, on the webgl branch:
https://github.com/tanis2000/minigame-rust/tree/webgl

The exact error is:

Uncaught abort("alignment fault") at Error
    at jsStackTrace (http://localhost:8000/main.js:1667:13)
    at stackTrace (http://localhost:8000/main.js:1684:12)
    at abort (http://localhost:8000/main.js:11888:44)
    at alignfault (http://localhost:8000/main.js:1053:3)
    at SAFE_HEAP_LOAD_i32_4_4 (wasm-function[171210]:39)
    at __ZN4core4cell13Cell_LT_T_GT_3get17ha7292d0f274620b2E (wasm-function[3605]:45)
    at __ZN5alloc2rc8RcBoxPtr6strong17h9d76bc796af30b74E (wasm-function[1084]:51)
    at __ZN5alloc2rc8RcBoxPtr10inc_strong17h974f8b274d2ef40bE (wasm-function[1089]:36)
    at __ZN61__LT_alloc__rc__Rc_LT_T_GT__u20_as_u20_core__clone__Clone_GT_5clone17h42ec3c5edf9a6750E (wasm-function[1102]:36)
    at dynCall_ii (wasm-function[5737]:14)

If I check the logs, I see that there seems to be some sort of memory corruption going on, but I don't know how to figure out what's causing it. Any pointers to tools or ways to debug this issue?

The weird thing is that it's working fie when compiled for desktop or mobile platforms.

Thanks in advance!

I've added a minimum reproduction of the issue in a different repository so that it's easier to have a look at: GitHub - tanis2000/rust-sdl2-wasm

I'm still struggling with this issue and I'm trying to narrow down the possible causes.

After a lot of trials and errors I came to the conclusion that the problem lies when I call out to unsafe code, in this specific case it's a simple call to two OpenGL functions:

        unsafe {
            gl::ClearColor(191.0/255.0, 255.0/255.0, 255.0/255.0, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }

Any idea why this would happen?

Sorry you're not getting responses, there are just a lot of moving parts to your question. I found your report against emscripten on GitHub, too, where they seemed very C++ centric.

Let me see if I can help get the discussion going, at least.

By itself, Rust avoids making unaligned memory accesses. Wasm explicitly supports them, so I guess the trap you're hitting is some emscripten thing? (I'm not even sure which direction to read your stack trace in; it doesn't look like any language I'm familiar with.)

Assuming you're not doing unsafe stuff, the problem is probably in the library you're using, the one providing the gl module. As wasm provides no GL interface, I assume this is also an emscripten thing.

You mention that the clear call doesn't work; does this mean other GL does work?

If no: Is there maybe an initializer function in the library that sets up static state that you haven't called, and it isn't checking?

Thanks for jumping in the wagon @cbiffle
It looks like this issue is quite complex indeed.

What I discovered so far is that I can as well just take SDL2 and all the OpenGL calls out of the code and I would still hit what looks like memory corruption.

As an example I commented out all of the SDL2 and GL calls and only left a println!("{}", dummy); let mut something = vec![1; 1_000_000]; in the main loop and I can still see that when the application starts in the browser an m character gets printed to the console log of the browser, just like if someone called a console.log('m'); but there's no code doing anything like that.

At some point it even started printing pieces of code to the console log so I'm definitely suspecting that there's a memory issue going on there.

On the other end, I am using Emscripten with different C projects (all of them using SDL2 btw) and none ever shown a similar issue. So this is making me think that Emscripten can't be the culprit. It looks more like a problem with the way Rust prepares the code to give to Emscripten or the way I set up the main loop callback. I even thought that eventually the resources could get released while the main loop is running because no one is retaining them, even though cargo web assures that main() is not terminating because they automatically an infinite loop before the end of the function.

Anyway I tried assing a std::mem::forget(callback); in the following code, just like suggested by @michaelfairley:

#[allow(non_camel_case_types)]
type em_callback_func = unsafe extern "C" fn();

extern "C" {
    // This extern is built in by Emscripten.
    pub fn emscripten_run_script_int(x: *const c_uchar) -> c_int;
    pub fn emscripten_cancel_main_loop();
    pub fn emscripten_set_main_loop(func: em_callback_func,
                                    fps: c_int,
                                    simulate_infinite_loop: c_int);
}

thread_local!(static MAIN_LOOP_CALLBACK: RefCell<*mut c_void> = RefCell::new(null_mut()));

pub fn set_main_loop_callback<F>(callback : F) where F : FnMut() {
    MAIN_LOOP_CALLBACK.with(|log| {
            *log.borrow_mut() = &callback as *const _ as *mut c_void;
            });

    std::mem::forget(callback);
    unsafe { emscripten_set_main_loop(wrapper::<F>, 0, 1); }

    unsafe extern "C" fn wrapper<F>() where F : FnMut() {
        MAIN_LOOP_CALLBACK.with(|z| {
            let closure = *z.borrow_mut() as *mut F;
            (*closure)();
        });
    }
}

But nothing changed, the code is still showing the same issues.

Here's the solution to this issue, in case anyone needs it. Basically we need to store the closure on the heap to avoid stack allocated memory from being freed and leading to undefined behavior. So here's the actual code, thanks again to @michaelfairley

#[allow(non_camel_case_types)]
type em_callback_func = unsafe extern "C" fn();

extern "C" {
    // This extern is built in by Emscripten.
    pub fn emscripten_run_script_int(x: *const c_uchar) -> c_int;
    pub fn emscripten_cancel_main_loop();
    pub fn emscripten_set_main_loop(func: em_callback_func,
                                    fps: c_int,
                                    simulate_infinite_loop: c_int);
}

thread_local!(static MAIN_LOOP_CALLBACK: RefCell<Option<Box<dyn FnMut()>>> = RefCell::new(None));

pub fn set_main_loop_callback<F: 'static>(callback : F) where F : FnMut() {
    MAIN_LOOP_CALLBACK.with(|log| {
        *log.borrow_mut() = Some(Box::new(callback));
    });

    unsafe { emscripten_set_main_loop(wrapper::<F>, 0, 1); }

    extern "C" fn wrapper<F>() where F : FnMut() {
        MAIN_LOOP_CALLBACK.with(|z| {
            if let Some(ref mut callback) = *z.borrow_mut() {
                callback();
            }
        });
    }
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.