WasmEdge / WasmEdge

WasmEdge is a lightweight, high-performance, and extensible WebAssembly runtime for cloud native, edge, and decentralized applications. It powers serverless apps, embedded functions, microservices, smart contracts, and IoT devices.

Home Page:https://WasmEdge.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

bug: The windows-MSVC job in the test wasmedge core workflow fails always

jaydee029 opened this issue · comments

Summary

The windows-MSVC job in the test wasmedge core workflow is failing for all PRs since the AOTcore and apiAOTcore files were edited 2 weeks ago. (just an observation not sure).

Current State

The last few PRs raised all fail this one check, even if it has no specific changes made to the related files for windows-msvc .
example
#3110
#3093 (closed)
and many more.

Expected State

The workflow passes correctly.

Reproduction steps

Make a PR that invokes test wasmedge core/ windows-msvc test.

Components

Others/ci

WasmEdge Version or Commit you used

latest at this moment

Hi @am009
Would you like to check this issue? It seems like the MSVC workflow of the AOT-related tests failed from a specific timestamp.

In a quite recent commit 238ac72, the test still works.

This issue is caused by two failures of TestUnit/NativeCoreTest.TestSuites/16 in wasmedgeAOTCoreTests and wasmedgeAPIAOTCoreTests:

1: [2023-12-26 11:38:24.387] [error] execution failed: out of bounds memory access, Code: 0x408
1: [2023-12-26 11:38:24.387] [error]     When executing module name: "exporter" , function name: "get"
1: E:\WasmEdgeDev\WasmEdge\test\spec\spectest.cpp(570): error: Expected: (LineNumber) != (LineNumber), actual: 670 vs 670
1: [2023-12-26 11:38:24.394] [error] execution failed: out of bounds table access, Code: 0x407
1: [2023-12-26 11:38:24.394] [error]     Accessing offset from: 0x7fcb9f00 to: 0x7fcb9f00 , Out of boundary: 0x00000001
1: [2023-12-26 11:38:24.394] [error] execution failed: out of bounds table access, Code: 0x407
1: [2023-12-26 11:38:24.394] [error]     When executing module name: "exporter" , function name: "get"
1: E:\WasmEdgeDev\WasmEdge\test\spec\spectest.cpp(570): error: Expected: (LineNumber) != (LineNumber), actual: 676 vs 676
1: [2023-12-26 11:38:24.394] [error] execution failed: out of bounds memory access, Code: 0x408
1: [2023-12-26 11:38:24.394] [error]     When executing module name: "exporter" , function name: "get"
1: E:\WasmEdgeDev\WasmEdge\test\spec\spectest.cpp(570): error: Expected: (LineNumber) != (LineNumber), actual: 677 vs 677
1: [  FAILED  ] TestUnit/NativeCoreTest.TestSuites/16, where GetParam() = "core elem" (277 ms)

I'll take a deeper look later.

After searching and debugging for a whole day, I finally found the reason.

The bug: The generated IR is correct, and the generated binary is also correct. The problem occurs at the proxy function here. When a wasm function calls function pointers in Executor::Intrinsics, it actually calls this proxy function which wraps functions like Executor::tableGet. Only the wrapper for tableGet is weird and will throw errors that table.get is out of bound, other functions in the Intrinsics table work correctly.

template <typename RetT, typename... ArgsT>
struct Executor::ProxyHelper<Expect<RetT> (Executor::*)(Runtime::StackManager &,
ArgsT...) noexcept> {
template <Expect<RetT> (Executor::*Func)(Runtime::StackManager &,
ArgsT...) noexcept>
static auto proxy(ArgsT... Args)
#if !WASMEDGE_OS_WINDOWS
noexcept
#endif
{
Expect<RetT> Res = (This->*Func)(*CurrentStack, Args...);
if (unlikely(!Res)) {
Fault::emitFault(Res.error());
}
if constexpr (!std::is_void_v<RetT>) {
return *Res;
}
}
};

By examining the assembly, it is expecting three arguments, instead of two. The C++ signature of this function is
WasmEdge::RefVariant ProxyHelper::proxy<&WasmEdge::Executor::Executor::tableGet>(unsigned int <Args_0>, unsigned int <Args_1>). It has only two arguments.

// decompiler output:
WasmEdge::RefVariant *__fastcall ___proxy__1_tableGet_Executor_2WasmEdge__QEAA_AV__expected_URefVariant_WasmEdge__VErrCode_2__cxx20__AEAVStackManager_Runtime_3_II_Z___ProxyHelper_P8Executor_1WasmEdge__EAA_AV__expected_URefVariant_WasmEdge__VErrCode_2__cxx20__AEAVStackManager_Runtime_2_II__E_Executor_1WasmEdge__SA_A_PII_Z(
        WasmEdge::RefVariant *result,
        unsigned int <Args_0>,
        unsigned int <Args_1>)
{
  ....
  if ( WasmEdge::unlikely_19(v14) )
  {
    ....
  }
  result->Ptr = cxx20::expected<WasmEdge::RefVariant,WasmEdge::ErrCode>::operator*(&v10)->Ptr;
  j__RTC_CheckStackVars(&v8, (_RTC_framedesc *)&unk_1435B7920);
  return result;
}

The reason:

I think the reason is C++'s copy and move elision. Only the proxy functions of tableGet and refFunc will directly return the struct RefVariant. I guess even if it is only 8 bytes, it is still returning an object, and the return value optimization will make the object exist only in callers stack, and add an additional argument to pass the pointer to the object.

Solutions:

  • #pragma optimize( "", off ): not working
  • adding /Zc:nrvo- when compiling proxy.cpp: not working So this is the mandatory case of copy and move elision.
  • Specialize the template for this case, and return the Ptr member in the RefVariant, making the return type void*. I drafted a PR here: #3129