Wasm-pack no-modules build have strange behaviours

Hi,
I'm working on a project in Rust for client-side computation in the browser. The project toolchain uses Webpack 4 and the workaround used to load the WASM into the JavaScript environment use the no-modules target.
My WASM function doesn't take any input, the input data are written in two Int32Array and one Float64Array directly into the WASM memory. After that the data is processed into one final Vec as a result.
I'm trying to return the final Vec to the JS. I've tried to return a slice, it doesn't work. I've also tried to return a js_sys::Int32Array and I only get random numbers between 26 and 70.

I don't know why I have these results, when I'm building in an example project with the web target everything is fine, I can return an Int32Array without problems.

The other thing I've found is that sometimes I need to add a 1 as an argument to my WASM functions, and I don't understand why, because my Rust code doesn't take that much args.

I know the no-modules target is old but that's some unexpected behavior.

I'm using the latest wasm-pack, rust nightly, and latest dependencies from crates.io.

Are you trying to directly load the .wasm file? That won't work. Even with a no-modules build you need to use the wrapper javascript: Without a Bundler - The `wasm-bindgen` Guide Webassembly only has integer and float types. For this reason wasm-bindgen uses a javascript wrapper to do things like pass opaque handles to the wasm and serialize objects into bytes to copy to the wasm linear memory and deserialize them back into objects. If you are using the wrapper javascript, would you mind posting the actual code you try to use or a smaller version that still reproduces the issue?

That's my code I will try to write a minimal example without the full webpack toolchain.

My code when I'm trying to read the WASM memory directly from the JS.

<html>
<head>
<script src="./Computation/./wasm.js"></script>
<script src="fr.js?version=1624258342"></script> <!-- Webpack generated JS from TypeScript -->
<!-- some CSS and JS dependencies. Jquery, socket.io and more -->
</head>
<body>
<!-- divs andpage layout stuff -->
</body>
declare let wasm_bindgen: any;
const wasm = wasm_bindgen("./Computation/wasm_bg.wasm");

class WasmGraph extends Graphs.Graph<Point, Data.ArcDTO> {
    protected _graphUpdate: boolean;
    protected _nodes: Array<any> = [];

    public constructor() {
        super(null);
        this._graphUpdate = true;
    }

    public clear() {
        super.clear();
        this._graphUpdate = true;
    }

    public addVertex(v: Graphs.Vertex<Point>): Graphs.Vertex<Point> {
        this._graphUpdate = true;
        return super.addVertex(v);
    }

    public addVertexAt(p: Point): Graphs.Vertex<Point> {
        this._graphUpdate = true;
        return super.addVertexAt(p);
    }

    public addArc(
        from: Graphs.Vertex<Point>, to: Graphs.Vertex<Point>, distance: number,
        dualDirection: boolean = true, edgeData: Data.ArcDTO = null) {
        this._graphUpdate = true;
        return super.addArc(from, to, distance, dualDirection, edgeData);
    }

    private async buildArrays() {
        let obj = await wasm;
        this._vertices.forEach((item, index) => {
            let j: Array<any> = [];
            item.neighbours.forEach((item) => {
                j.push({
                    id: 0,
                    distance: item.distance,
                    position: item.to.position
                });
            });
            let i = {
                id: index,
                neighbours: j,
                position: item.position
            };
            this._nodes.push(i);
        });
        let no: Array<any> = [];
        let ar: Array<any> = [];
        let arl: Array<any> = [];
        this._nodes.forEach((item) => {
            item.neighbours.forEach((i: any) => {
                this._nodes.forEach((j) => {
                    if (i.position == j.position) {
                        i.id = j.id;
                    }
                });
            });
            no.push(ar.length);
            item.neighbours.forEach((i: any) => {
                ar.push(i.id);
                arl.push(i.distance);
            });
        });
        let nodes = new Int32Array(obj.memory.buffer, obj.nodes_ptr(), no.length);
        let arcs = new Int32Array(obj.memory.buffer, obj.arcs_ptr(), ar.length);
        let arc_length = new Float64Array(obj.memory.buffer, obj.arc_length_ptr(), arl.length);
        let new_no: Array<any> = [];
        no.forEach((item, index) =>{
            if (item == no[index - 1]) {
                new_no[index] = -1;
            }
            else {
                new_no[index] = item;
            }
        });
        ar.forEach((item, index) => {
            arcs[index] = item;
        });
        new_no.forEach((item, index) => {
            nodes[index] = item;
        });
        arl.forEach((item, index) => {
            arc_length[index] = item;
        });
    }

    public async getWasmPath(from: Graphs.Vertex<Point>, to: Graphs.Vertex<Point>, canUseEdge?: (edge: Graphs.Edge<any, any>) => boolean):
        Promise<Graphs.VertexAndEdge<Point, Data.ArcDTO>[]> {
        if (this._graphUpdate) {
            this.buildArrays();
            this._graphUpdate = false;
        }
        let mod = await wasm;
        let result = new Int32Array(mod.memory.buffer, mod.result_ptr(), 4900000);
        let start: number, end: number;
        this._nodes.forEach((item) => {
            if (item.position == from.position) {
                if (item.id == 0) {
                    start = item.id + 1;
                }
                else {
                    start = item.id;
                }
            }
            else if (item.position == to.position) {
                if (item.id == 0) {
                    end = item.id + 1;
                }
                else {
                    end = item.id;
                }
            }
        });
        let length = await mod.get_path(start, end);
        let res = [];
        let index = 0;
        while (index < length) {
            res[index] = result[index];
            index++;
        }
        console.log(res, length);
        return null; // Because, I'm testing stuff.
    }
}
#![feature(total_cmp)] // For the dijkstra part

use console_error_panic_hook;
use wasm_bindgen::prelude::*;

mod dijkstra;
mod utils;

static NODES: [i32; 4900000 as usize] = [-2; 4900000 as usize];
static ARCS: [i32; 4900000 as usize] = [-2; 4900000 as usize];
static ARCLENGTH: [f64; 4900000 as usize] = [-2.0; 4900000 as usize];
static mut RESULT: [i32; 4900000 as usize] = [-2; 4900000 as usize];

#[wasm_bindgen]
pub fn nodes_ptr() -> *const i32 {
    NODES.as_ptr()
}

#[wasm_bindgen]
pub fn arcs_ptr() -> *const i32 {
    ARCS.as_ptr()
}

#[wasm_bindgen]
pub fn arc_length_ptr() -> *const f64 {
    ARCLENGTH.as_ptr()
}

#[wasm_bindgen]
pub fn result_ptr() -> *const i32 {
    unsafe { RESULT.as_ptr() }
}

#[wasm_bindgen]
pub fn get_path(from: i32, to: i32) -> usize {
    console_error_panic_hook::set_once();
    let mut nodes = Vec::new();
    for i in &NODES {
        if *i == -2 as i32 {
            continue;
        } else {
            nodes.push(*i);
        }
    }
    let mut arcs = Vec::new();
    for i in &ARCS {
        if *i == -2 as i32 {
            continue;
        } else {
            arcs.push(*i);
        }
    }
    let mut arcs_lengths = Vec::new();
    for i in &ARCLENGTH {
        if *i == -2 as f64 {
            continue;
        } else {
            arcs_lengths.push(*i);
        }
    }
    log!("{:?}, {:?}", from, to);
    let path = match dijkstra::dijkstra(nodes, arcs, arcs_lengths, from, to) {
        Ok(res) => res,
        Err(e) => {
            log!("{}", e);
            return 0 as usize;
        }
    };
    log!("{:?}", path.0);
    let mut j = 0;
    for i in path.0.as_slice() {
        unsafe { RESULT[j] = *i }
        j += 1;
    }
    return path.0.len();
}

At the very least you will need to make the NODES, ARCS and ARCLENGTH statics mutable and use .as_mut_ptr() in the *_ptr functions. Otherwise LLVM will assume that the statics never change and optimize accordingly. I am not sure if that will fix the problem though.

That's a good idea.
It doesn't change the results.

Here is my minimal not working as expected example.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn return_array() -> js_sys::Int32Array {
    let vector = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let result = js_sys::Int32Array::new_with_length(vector.len() as u32);
    result.copy_from(vector.as_slice());
    return result;
}
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<script src="pkg/broken.js"></script>
	<script type="text/javascript">
		const wasm = wasm_bindgen("./pkg/broken_bg.wasm");
		async function run() {
			let mod = await wasm;
			console.log(mod.return_array());
		}
		run()
	</script>
</head>
<body>
</body>
[package]
name = "broken"
version = "0.1.0"
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"

[dev-dependencies]

and when I serve and load the index I only get 36 in the console. When I use my other example (nothing change except the loading in the html file and the build target) I get the full array in the console.

:confused: I have no idea what the problem is. Maybe someone who is more familiar with wasm-bindgen knows.

I've found a solution by moving the let result = new Int32Array(mod.memory.buffer, mod.result_ptr(), 4900000); after the let length = await mod.get_path(start, end);.

But that doesn't explain why it work like that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.