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

bug: AOT compiler inconsistent memory side-effect with traps #3347

Open
candymate opened this issue Apr 19, 2024 · 0 comments
Open

bug: AOT compiler inconsistent memory side-effect with traps #3347

candymate opened this issue Apr 19, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@candymate
Copy link

Summary

AOT compiler shows inconsistent memory side-effects when trap occurs. The PoC code is the following:

(module
  (type (;0;) (func))
  (func (;0;) (type 0)
    i32.const 0
    i32.const 0
    v128.const i32x4 0xffffffff 0xffffffff 0xffffffff 0xffffffff
    v128.store offset=4 align=2 ;; offset larger than 4 (offset 4 is tainted)
    i32.const 4
    i32.const -1
    i32.load align=1 ;; this load fails (traps here)
    i32.eq
    v128.load8_splat align=1
    v128.store offset=4 align=2) ;; same offset
  (memory (;0;) 1)
  (export "mem" (memory 0))
  (export "main" (func 0)))

There are three noticable points: two v128.store and one i32.load. First, the first v128.store writes value to the memory at offset 4. Then, 16 bytes from offset 4 should be filled with 0xff. Second, the program traps at i32.load due to invalid offset -1. Then, the program should terminate while having memory side-effect at offset 4.

However, due to optimizations, at O2 and higher, memory is left as unwritten at the time the program traps. My guess here is that due to instruction scheduling, the order of two v128.stores are switched so that the memory side-effect did not occur. To explain more, the trap occurs first before the memory is written at 0x4.

Current State

Using AOT compiler and executing the above wasm program gives the memory side-effect inconsistently. For O1 and non-AOT, the memory at offset 4 is properly written with 0xff. (total 16 bytes, offset 4~19) For O2 to Oz, the memory is unchanged.

That is, the expected memory state at the end is the following:

[0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, ...]

However, the memory becomes like:

[0, 0, 0, 0, 0, 0, ...]

Expected State

The value 0xff should be written at offset 4~19 at the time when the program terminates. This should be done regardless of optimizations.

Reproduction steps

Run with:

(I did not check this code - but it will work..)

use wasmedge_sdk::config::CommonConfigOptions;
use wasmedge_sdk::config::CompilerConfigOptions;
use wasmedge_sdk::config::ConfigBuilder;
use wasmedge_sdk::types::Val;
use wasmedge_sdk::Compiler;
use wasmedge_sdk::CompilerOptimizationLevel;
use wasmedge_sdk::CompilerOutputFormat;
use wasmedge_sdk::LogManager;
use wasmedge_sdk::Module;
use wasmedge_sdk::VmBuilder;
use anyhow::{bail, Error};

fn main() -> Result<(), Error> {
    LogManager::log_off();
    
    let config = ConfigBuilder::new(CommonConfigOptions::default())
        .with_compiler_config(
            CompilerConfigOptions::default()
                .optimization_level(CompilerOptimizationLevel::O0) // change here for optimization level
                .out_format(CompilerOutputFormat::Native))
        .build()?;
    let compiler = Compiler::new(Some(&config))?;
    let aot_file_path = compiler.compile_from_file("test.wasm", "aot", ".")?;
    let module = Module::from_file(Some(&config), aot_file_path)?;
    let vm = VmBuilder::new().with_config(config).build()?.register_module(None, module)?;

    let instance = vm.active_module()?;
    let main = match instance.func_names() {
        Some(x) => instance.func(x[0].clone())?,
        None => bail!("There is no exported function"),
    };
    
    let params: [Val; 0] = Default::default();
    match main.run(vm.executor(), params.into_iter().map(|x| x.into())) {
        Ok(results) => println!("{:?}", results),
        Err(_e) => println!("Error"),
    }
    let memory = match instance.memory_names() {
        Some(x) => Some(instance.memory(x[0].clone())?),
        None => None,
    };
    println!("{:?}", memory_inner.read(0, memory_inner.page()*65536)?);

    Ok(())
}

Screenshots

DESCRIPTION

Any logs you want to share for showing the specific issue

No response

Components

Rust SDK

WasmEdge Version or Commit you used

0.13.5

Operating system information

Ubuntu 22.04

Hardware Architecture

x86_64

Compiler flags and options

Cmake version: 3.22.1
Command: cmake -DCMAKE_BUILD_TYPE=Release -DWASMEDGE_BUILD_TESTS=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" .. && make -j
Cargo, rustc: 1.79.0-nightly

@candymate candymate added the bug Something isn't working label Apr 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant