wasmerio/wasmer

llvm: v128 loads with invalid memory offsets inconsistently traps

Opened this issue · 0 comments

Describe the bug

The execution should trap for the variants of v128.load (e.g., v128.load32x2_s) in llvm compiler even if the result of it is dropped. However, it executes with no errors, no traps occuring. This behavior is inconsistent with Cranelift compiler since it traps with "HeapAccessOutOfBounds" normally, no matter which optimization level is given.

use wasmer::sys::{EngineBuilder, Features};
use wasmer::{imports, Cranelift, CraneliftOptLevel, Instance, Memory, MemoryType, Module, Store, CompilerConfig};
use wasmer_compiler_llvm::{LLVM, LLVMOptLevel};
use anyhow::Error;

fn main() -> Result<(), Error> {
    let diff = 1; // CHANGE HERE!!! (0 and 1)

    let mut features = Features::new();
    features.multi_value(true);
    features.simd(true);
    features.threads(true);

    let engine = if diff == 0 {
        let mut compiler = Cranelift::default();
        compiler.canonicalize_nans(true);
        compiler.opt_level(CraneliftOptLevel::None);

        let engine = EngineBuilder::new(compiler).set_features(Some(features));
        engine
    } else {
        let mut compiler = LLVM::default();
        compiler.canonicalize_nans(true);
        compiler.opt_level(LLVMOptLevel::None);
        
        let engine = EngineBuilder::new(compiler).set_features(Some(features));
        engine
    };

    let module_wat = r#"
    (module
        (type (;0;) (func))
        (import "mem" "mem" (memory (;0;) 1))
        (func (;0;) (type 0)
          i32.const 0xffffffff   ;; invalid memory offset
          v128.load32x2_s align=1 ;; any load variants
          drop)
        (export "main" (func 0)))
    "#;
    // let module_wat = r#"
    // (module
    //     (type (;0;) (func))
    //     (import "mem" "mem" (memory (;0;) 1))
    //     (func (;0;) (type 0)
    //       i32.const 0xffffffff
    //       v128.load align=1
    //       drop)
    //     (export "main" (func 0)))
    // "#;

    let mut store = Store::new(engine);
    let module = Module::new(&store, &module_wat)?;

    let memory_ty = MemoryType::new(1, None, false);
    let memory = Memory::new(&mut store, memory_ty)?;

    let imports = imports!{
        "mem" => {
            "mem" => memory.clone()
        }
    };

    let instance = Instance::new(&mut store, &module, &imports)?;
    let main = instance.exports.get_function("main")
        .expect("`main` was not an exported function");
    let result = main.call(&mut store, vec![].as_slice());
    
    println!("{:?}", result);

    Ok(())
}
[package]
name = "wasmer-wrapper"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasmer = { version = "4.2.8", features = ["cranelift", "singlepass", "compiler"] }
wasmer-compiler-llvm = "4.2.8"
anyhow = "1.0"

The behavior is even not consistent with v128.load. The execution result ends with "HeapAccessOutOfBounds" even when we use llvm compiler.

Steps to reproduce

Change the diff value to test different compiler options. (Line 7, values with 0 and 1) Also, you can comment and uncomment the module_wat variable as needed.

cargo run --release

Expected behavior

All executions, whichever the type of load instruction and compiler options is provided, they should trap with "HeapAccessOutOfBounds" error.

Actual behavior

Executions with variants of v128.load and llvm compiler end with no traps. However, executions with the same code with Cranelift compiler, having the variants of v128.load, ends with traps. Also, executions with v128.load, which is not a variant, end with traps with llvm and Cranelift compiler.

Additional context

  • llvm version: 15.0.7