Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does wasm-opt fail in wasm-pack builds when generating a function returning a string?

I'm working through the Rust WASM tutorial for Conway's game of life.

One of the simplest functions in the file is called Universe.render (it's the one for rendering a string representing game state). It's causing an error when I run wasm-pack build:

Fatal: error in validating input
Error: failed to execute `wasm-opt`: exited with exit code: 1
  full command: "/home/vaer/.cache/.wasm-pack/wasm-opt-4d7a65327e9363b7/wasm-opt" "/home/vaer/src/learn-rust/wasm-game-of-life/pkg/wasm_game_of_life_bg.wasm" "-o" "/home/vaer/src/learn-rust/wasm-game-of-life/pkg/wasm_game_of_life_bg.wasm-opt.wasm" "-O"
To disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.

If I remove that function, the code builds without errors. If I replace it with the following function, the build fails with the same error:

pub fn wtf() -> String {
    String::from("wtf")
}

It seems like any function that returns a String causes this error. Why?

Following is the entirety of my code:

mod utils;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

// Begin game of life impl

use std::fmt;

#[wasm_bindgen]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cell {
    Dead = 0,
    Alive = 1,
}

#[wasm_bindgen]
pub struct Universe {
    width: u32,
    height: u32,
    cells: Vec<Cell>,
}

impl fmt::Display for Universe {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for line in self.cells.as_slice().chunks(self.width as usize) {
            for &cell in line {
                let symbol = if cell == Cell::Dead { '◻' } else { '◼' };
                write!(f, "{}", symbol)?;
            }
            write!(f, "\n")?;
        }

        Ok(())
    }
}

impl Universe {
    fn get_index(&self, row: u32, column: u32) -> usize {
        (row * self.width + column) as usize
    }

    fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
        let mut count = 0;
        for delta_row in [self.height - 1, 0, 1].iter().cloned() {
            for delta_col in [self.width - 1, 0, 1].iter().cloned() {
                if delta_row == 0 && delta_col == 0 {
                    continue;
                }

                let neighbor_row = (row + delta_row) % self.height;
                let neighbor_col = (column + delta_col) % self.width;
                let idx = self.get_index(neighbor_row, neighbor_col);
                count += self.cells[idx] as u8;
            }
        }
        count
    }
}

/// Public methods, exported to JavaScript.
#[wasm_bindgen]
impl Universe {
    pub fn tick(&mut self) {
        let mut next = self.cells.clone();

        for row in 0..self.height {
            for col in 0..self.width {
                let idx = self.get_index(row, col);
                let cell = self.cells[idx];
                let live_neighbors = self.live_neighbor_count(row, col);

                let next_cell = match (cell, live_neighbors) {
                    // Rule 1: Any live cell with fewer than two live neighbours
                    // dies, as if caused by underpopulation.
                    (Cell::Alive, x) if x < 2 => Cell::Dead,
                    // Rule 2: Any live cell with two or three live neighbours
                    // lives on to the next generation.
                    (Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
                    // Rule 3: Any live cell with more than three live
                    // neighbours dies, as if by overpopulation.
                    (Cell::Alive, x) if x > 3 => Cell::Dead,
                    // Rule 4: Any dead cell with exactly three live neighbours
                    // becomes a live cell, as if by reproduction.
                    (Cell::Dead, 3) => Cell::Alive,
                    // All other cells remain in the same state.
                    (otherwise, _) => otherwise,
                };

                next[idx] = next_cell;
            }
        }

        self.cells = next;
    }

    pub fn new() -> Universe {
        let width = 64;
        let height = 64;

        let cells = (0..width * height)
            .map(|i| {
                if i % 2 == 0 || i % 7 == 0 {
                    Cell::Alive
                } else {
                    Cell::Dead
                }
            })
            .collect();

        Universe {
            width,
            height,
            cells,
        }
    }

    pub fn render(&self) -> String {
        self.to_string()
    }
}

Simply removing the render function at the bottom of this file causes the build to succeed. Replacing the render function with any function returning a String causes the build to fail. Why?

like image 740
vaer-k Avatar asked Oct 23 '20 21:10

vaer-k


People also ask

Why is my Wasm binary size so big?

One of the biggest contributors to wasm binary size can be debug information and the names section of the wasm binary. The wasm-pack tool, however, removes debuginfo by default. Additionally wasm-opt removes the names section by default unless -g is also specified.

Does Wasm-opt remove the names section by default?

Additionally wasm-opt removes the names section by default unless -g is also specified. This means that if you follow the above steps you should by default not have either debuginfo or the names section in the wasm binary. If, however, you are manually otherwise preserving this debug information in the wasm binary be sure to be mindful of this!

How to disable Wasm-opt for a target?

Finished release [optimized] target (s) in 0.50s [INFO]: Installing wasm-bindgen... Error: no prebuilt wasm-opt binaries are available for this platform: Unrecognized target! To disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.

What is the difference between WASM-snip and dynamic dispatch via virtual calls?

Then dynamic dispatch via virtual calls is used, and only a single version of the function is emitted in the .wasm. The downside is the loss of the compiler optimization opportunities and the added cost of indirect, dynamically dispatched function calls. wasm-snip replaces a WebAssembly function's body with an unreachable instruction.


1 Answers

It turns out that this is not expected behavior; instead it is a bug with wasm-pack.

The issue can be resolved for now by adding the following to the project's cargo.toml:

[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-Oz", "--enable-mutable-globals"]
like image 125
vaer-k Avatar answered Oct 18 '22 22:10

vaer-k