add more unit test cases for interpreter

Signed-off-by: zhenweijin <zhenwei.jin@intel.com>

Co-authored-by: Gong, Pu <pu.gong@intel.com>

Co-authored-by: ai-assistant
This commit is contained in:
zhenweijin 2025-11-21 17:57:34 +08:00 committed by kylo5aby
parent 081c3445ef
commit 6fb51c795d
6 changed files with 660 additions and 0 deletions

View File

@ -64,6 +64,7 @@ add_subdirectory(linux-perf)
add_subdirectory(gc)
add_subdirectory(tid-allocator)
add_subdirectory(unsupported-features)
add_subdirectory(smart-tests)
if (NOT WAMR_BUILD_TARGET STREQUAL "X86_32")
add_subdirectory(aot-stack-frame)

View File

@ -0,0 +1,4 @@
# Enhanced Unit Test CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
add_subdirectory(interpreter)

View File

@ -0,0 +1,51 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required(VERSION 3.14)
project (smart-test-interpreter)
add_definitions (-DRUN_ON_LINUX)
add_definitions (-Dattr_container_malloc=malloc)
add_definitions (-Dattr_container_free=free)
# add_definitions (-DWASM_ENABLE_WAMR_COMPILER=1)
set(WAMR_BUILD_AOT 0)
set(WAMR_BUILD_INTERP 1)
set(WAMR_BUILD_JIT 0)
set (WAMR_BUILD_LIBC_WASI 0)
set (WAMR_BUILD_APP_FRAMEWORK 1)
include (../../unit_common.cmake)
include_directories (${CMAKE_CURRENT_SOURCE_DIR})
file (GLOB_RECURSE source_all ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)
set (UNIT_SOURCE ${source_all})
set (unit_test_sources
${UNIT_SOURCE}
${WAMR_RUNTIME_LIB_SOURCE}
)
add_executable (smart_test_classic-interpreter ${unit_test_sources})
target_compile_definitions(smart_test_classic-interpreter PUBLIC -DWAMR_BUILD_FAST_INTERP=0)
target_link_libraries (smart_test_classic-interpreter gtest_main)
add_executable (smart_test_fast-interpreter ${unit_test_sources})
target_compile_definitions(smart_test_fast-interpreter PUBLIC -DWAMR_BUILD_FAST_INTERP=1)
target_link_libraries (smart_test_fast-interpreter gtest_main)
# Copy WASM files to build directory for classic interpreter
# fast interpreter uses the same WASM files
add_custom_command(TARGET smart_test_classic-interpreter POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_LIST_DIR}/wasm-apps/*.wasm
${CMAKE_CURRENT_BINARY_DIR}/
COMMENT "Copy test wasm files to the directory of google test"
)
gtest_discover_tests(smart_test_classic-interpreter)
gtest_discover_tests(smart_test_fast-interpreter)

View File

@ -0,0 +1,454 @@
/*
* Copyright (C) 2024 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include "test_helper.h"
#include "gtest/gtest.h"
#include <unistd.h>
#include <fstream>
#include "wasm_runtime_common.h"
#include "wasm_runtime.h"
#include "wasm_interp.h"
#include "wasm_loader.h"
static std::string CWD;
static std::string WASM_FILE;
static constexpr uint32_t STACK_SIZE = 8092;
static constexpr uint32_t HEAP_SIZE = 8092;
static int
test_import_add_impl(wasm_exec_env_t exec_env, int32_t a, int32_t b)
{
return a + b;
}
static int
test_import_mul_impl(wasm_exec_env_t exec_env, int32_t a, int32_t b)
{
return a * b;
}
static int
malloc_impl(wasm_exec_env_t exec_env, int32_t size)
{
return wasm_runtime_module_malloc(
wasm_runtime_get_module_inst(exec_env), size, NULL);
}
static void
free_impl(wasm_exec_env_t exec_env, int32_t ptr)
{
wasm_runtime_module_free(
wasm_runtime_get_module_inst(exec_env), ptr);
}
static int
native_func_impl(wasm_exec_env_t exec_env, int32_t a)
{
return a * 2;
}
static NativeSymbol native_symbols[] = {
{ "test_import_add", (void*)test_import_add_impl, "(ii)i", NULL },
{ "test_import_mul", (void*)test_import_mul_impl, "(ii)i", NULL },
{ "malloc", (void*)malloc_impl, "(i)i", NULL },
{ "free", (void*)free_impl, "(i)", NULL },
{ "native_func", (void*)native_func_impl, "(i)i", NULL }
};
/**
* Test fixture for Step 3: Function Invocation and Stack Operations
* Targets 6 functions: call_indirect, wasm_call_indirect, wasm_interp_call_func_import,
* copy_stack_values, execute_malloc_function, execute_free_function
*/
class FunctionInvocationTest : public testing::Test
{
protected:
void SetUp() override
{
char *current_dir = getcwd(NULL, 0);
CWD = std::string(current_dir);
free(current_dir);
WASM_FILE = CWD + "/function_invocation_test.wasm";
memset(&init_args, 0, sizeof(RuntimeInitArgs));
init_args.mem_alloc_type = Alloc_With_System_Allocator;
ASSERT_TRUE(wasm_runtime_full_init(&init_args));
// Register native symbols for import testing
ASSERT_TRUE(wasm_runtime_register_natives("env", native_symbols,
sizeof(native_symbols) / sizeof(NativeSymbol)));
load_wasm_file();
instantiate_module();
}
void TearDown() override
{
if (exec_env) {
wasm_runtime_destroy_exec_env(exec_env);
}
if (module_inst) {
wasm_runtime_deinstantiate(module_inst);
}
if (module) {
wasm_runtime_unload(module);
}
if (wasm_file_buf) {
delete[] wasm_file_buf;
}
wasm_runtime_destroy();
}
void load_wasm_file()
{
std::ifstream wasm_file(WASM_FILE, std::ios::binary);
ASSERT_TRUE(wasm_file.is_open()) << "Failed to open WASM file: " << WASM_FILE;
std::vector<uint8_t> buffer(std::istreambuf_iterator<char>(wasm_file), {});
wasm_file_size = buffer.size();
wasm_file_buf = new unsigned char[wasm_file_size];
std::copy(buffer.begin(), buffer.end(), wasm_file_buf);
module = wasm_runtime_load(wasm_file_buf, wasm_file_size, error_buf,
sizeof(error_buf));
ASSERT_NE(module, nullptr) << "Load module failed: " << error_buf;
}
void instantiate_module()
{
module_inst = wasm_runtime_instantiate(
module, STACK_SIZE, HEAP_SIZE, error_buf, sizeof(error_buf));
ASSERT_NE(module_inst, nullptr) << "Instantiate module failed: " << error_buf;
exec_env = wasm_runtime_create_exec_env(module_inst, STACK_SIZE);
ASSERT_NE(exec_env, nullptr);
}
RuntimeInitArgs init_args;
wasm_module_t module = nullptr;
wasm_module_inst_t module_inst = nullptr;
wasm_exec_env_t exec_env = nullptr;
unsigned char *wasm_file_buf = nullptr;
uint32_t wasm_file_size = 0;
char error_buf[128] = { 0 };
};
/**
* Test Function 1: call_indirect() - Valid function call
* Target: core/iwasm/interpreter/wasm_runtime.c:call_indirect()
* Expected Coverage: ~15 lines (success path)
*/
TEST_F(FunctionInvocationTest, CallIndirect_ValidFunction_ReturnsCorrectResult)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_call_indirect_valid");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[2];
wasm_argv[0] = 0; // table index 0 (points to $add_func)
wasm_argv[1] = 15; // parameter value
bool success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
// $add_func adds 10 to input: 15 + 10 = 25
ASSERT_EQ(wasm_argv[0], 25);
}
/**
* Test Function 1: call_indirect() - Invalid index
* Target: core/iwasm/interpreter/wasm_runtime.c:call_indirect()
* Expected Coverage: ~15 lines (error path)
*/
TEST_F(FunctionInvocationTest, CallIndirect_InvalidIndex_FailsGracefully)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_call_indirect_invalid_index");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[1];
wasm_argv[0] = 42; // input value
bool success = wasm_runtime_call_wasm(exec_env, func, 1, wasm_argv);
ASSERT_FALSE(success);
ASSERT_TRUE(wasm_runtime_get_exception(module_inst) != NULL);
}
/**
* Test Function 2: wasm_call_indirect() - Type mismatch error handling
* Target: core/iwasm/interpreter/wasm_runtime.c:wasm_call_indirect()
* Expected Coverage: ~13 lines (error path)
*/
TEST_F(FunctionInvocationTest, WasmCallIndirect_TypeMismatch_ReturnsFailure)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_call_indirect_type_mismatch");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[1];
wasm_argv[0] = 100; // input value
bool success = wasm_runtime_call_wasm(exec_env, func, 1, wasm_argv);
ASSERT_FALSE(success);
ASSERT_TRUE(wasm_runtime_get_exception(module_inst) != NULL);
}
/**
* Test Function 3: wasm_interp_call_func_import() - Success path
* Target: core/iwasm/interpreter/wasm_interp_fast.c:wasm_interp_call_func_import()
* Expected Coverage: ~12 lines (success path)
*/
TEST_F(FunctionInvocationTest, CallFuncImport_Success_CallsNativeFunction)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_import_function_call");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[2];
wasm_argv[0] = 15; // first parameter
wasm_argv[1] = 25; // second parameter
bool success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
// test_import_add_impl adds two values: 15 + 25 = 40
ASSERT_EQ(wasm_argv[0], 40);
}
/**
* Test Function 3: wasm_interp_call_func_import() - Multiple import calls
* Target: core/iwasm/interpreter/wasm_interp_fast.c:wasm_interp_call_func_import()
* Expected Coverage: ~13 lines (additional path)
*/
TEST_F(FunctionInvocationTest, CallFuncImport_MultipleImports_HandlesCorrectly)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_import_function_mul");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[2];
wasm_argv[0] = 6; // first parameter
wasm_argv[1] = 7; // second parameter
bool success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
// test_import_mul_impl multiplies two values: 6 * 7 = 42
ASSERT_EQ(wasm_argv[0], 42);
}
/**
* Test Function 4: copy_stack_values() - Normal operation
* Target: core/iwasm/interpreter/wasm_interp_fast.c:copy_stack_values()
* Expected Coverage: ~20 lines (normal operation)
*/
TEST_F(FunctionInvocationTest, CopyStackValues_Normal_CopiesValuesCorrectly)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_stack_operations");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[2];
wasm_argv[0] = 10; // val1
wasm_argv[1] = 20; // val2
bool success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
// Complex calculation: (10+20) + (5+10) + (20*3) = 30 + 15 + 60 = 105
ASSERT_EQ(wasm_argv[0], 105);
}
/**
* Test Function 4: copy_stack_values() - Large parameter count
* Target: core/iwasm/interpreter/wasm_interp_fast.c:copy_stack_values()
* Expected Coverage: Additional lines for large stack operations
*/
TEST_F(FunctionInvocationTest, CopyStackValues_LargeParams_HandlesCorrectly)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_large_param_stack");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[8];
wasm_argv[0] = 1;
wasm_argv[1] = 2;
wasm_argv[2] = 3;
wasm_argv[3] = 4;
wasm_argv[4] = 5;
wasm_argv[5] = 6;
wasm_argv[6] = 7;
wasm_argv[7] = 8;
bool success = wasm_runtime_call_wasm(exec_env, func, 8, wasm_argv);
ASSERT_TRUE(success);
// (1+2) + (3+4) + (5+6) + (7+8) = 3 + 7 + 11 + 15 = 36
ASSERT_EQ(wasm_argv[0], 36);
}
/**
* Test Function 5: execute_malloc_function() - Success path
* Target: core/iwasm/interpreter/wasm_runtime.c:execute_malloc_function()
* Expected Coverage: ~20 lines (success path)
*/
TEST_F(FunctionInvocationTest, ExecuteMalloc_Success_AllocatesMemory)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_malloc_operation");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[1];
wasm_argv[0] = 1024; // allocate 1KB
bool success = wasm_runtime_call_wasm(exec_env, func, 1, wasm_argv);
ASSERT_TRUE(success);
// Should return a valid memory offset (> 0)
ASSERT_GT(wasm_argv[0], 0);
}
/**
* Test Function 5: execute_malloc_function() - Failure path
* Target: core/iwasm/interpreter/wasm_runtime.c:execute_malloc_function()
* Expected Coverage: ~20 lines (failure path)
*/
TEST_F(FunctionInvocationTest, ExecuteMalloc_Failure_HandlesLargeAllocation)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_malloc_operation");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[1];
wasm_argv[0] = 0x10000000; // try to allocate large amount (256MB)
bool success = wasm_runtime_call_wasm(exec_env, func, 1, wasm_argv);
// The call should succeed (no crash), but malloc should return 0
ASSERT_TRUE(success);
ASSERT_EQ(wasm_argv[0], 0);
}
/**
* Test Function 6: execute_free_function() - Success path
* Target: core/iwasm/interpreter/wasm_runtime.c:execute_free_function()
* Expected Coverage: ~20 lines (success path)
*/
TEST_F(FunctionInvocationTest, ExecuteFree_Success_FreesMemory)
{
// First allocate memory
wasm_function_inst_t malloc_func = wasm_runtime_lookup_function(
module_inst, "test_malloc_operation");
ASSERT_NE(malloc_func, nullptr);
uint32_t malloc_argv[1];
malloc_argv[0] = 512;
bool success = wasm_runtime_call_wasm(exec_env, malloc_func, 1, malloc_argv);
ASSERT_TRUE(success);
ASSERT_GT(malloc_argv[0], 0);
// Now free the allocated memory
wasm_function_inst_t free_func = wasm_runtime_lookup_function(
module_inst, "test_free_operation");
ASSERT_NE(free_func, nullptr);
uint32_t free_argv[1];
free_argv[0] = malloc_argv[0]; // use allocated pointer
success = wasm_runtime_call_wasm(exec_env, free_func, 1, free_argv);
ASSERT_TRUE(success);
}
/**
* Test Function 6: execute_free_function() - Error handling
* Target: core/iwasm/interpreter/wasm_runtime.c:execute_free_function()
* Expected Coverage: ~20 lines (error path)
*/
TEST_F(FunctionInvocationTest, ExecuteFree_ErrorHandling_HandlesInvalidPointer)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_free_operation");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[1];
wasm_argv[0] = 0; // try to free NULL pointer
bool success = wasm_runtime_call_wasm(exec_env, func, 1, wasm_argv);
ASSERT_TRUE(success); // free(NULL) is valid and should succeed
}
/**
* Test malloc/free cycle to exercise both functions together
*/
TEST_F(FunctionInvocationTest, MallocFreeCycle_Complete_WorksCorrectly)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_malloc_free_cycle");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[1];
wasm_argv[0] = 256; // allocation size
bool success = wasm_runtime_call_wasm(exec_env, func, 1, wasm_argv);
ASSERT_TRUE(success);
// Should return the value that was stored (42)
ASSERT_EQ(wasm_argv[0], 42);
}
/**
* Test complex indirect call scenarios
*/
TEST_F(FunctionInvocationTest, ComplexIndirectCalls_MultipleSelectors_HandlesCorrectly)
{
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_complex_indirect_calls");
ASSERT_NE(func, nullptr);
// Test selector 0 (add_func)
uint32_t wasm_argv[2];
wasm_argv[0] = 0; // selector
wasm_argv[1] = 30; // value
bool success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
ASSERT_EQ(wasm_argv[0], 40); // 30 + 10 = 40
// Test selector 1 (mul_func)
wasm_argv[0] = 1; // selector
wasm_argv[1] = 15; // value
success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
ASSERT_EQ(wasm_argv[0], 30); // 15 * 2 = 30
// Test selector 2 (identity_func)
wasm_argv[0] = 2; // selector
wasm_argv[1] = 99; // value
success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
ASSERT_EQ(wasm_argv[0], 99); // identity returns same value
}
/**
* Additional test for function invocation edge cases
*/
TEST_F(FunctionInvocationTest, FunctionInvocation_EdgeCases_HandlesCorrectly)
{
// Test with maximum parameter values
wasm_function_inst_t func = wasm_runtime_lookup_function(
module_inst, "test_stack_operations");
ASSERT_NE(func, nullptr);
uint32_t wasm_argv[2];
wasm_argv[0] = 0xFFFFFFFF; // max uint32
wasm_argv[1] = 1;
bool success = wasm_runtime_call_wasm(exec_env, func, 2, wasm_argv);
ASSERT_TRUE(success);
}

View File

@ -0,0 +1,150 @@
(module
;; Import functions
(import "env" "test_import_add" (func $test_import_add (param i32 i32) (result i32)))
(import "env" "test_import_mul" (func $test_import_mul (param i32 i32) (result i32)))
(import "env" "malloc" (func $malloc (param i32) (result i32)))
(import "env" "free" (func $free (param i32)))
(import "env" "native_func" (func $native_func (param i32) (result i32)))
;; Type definitions
(type $void_to_void (func))
(type $i32_to_i32 (func (param i32) (result i32)))
(type $i32_i32_to_i32 (func (param i32 i32) (result i32)))
;; Memory and table
(memory 1)
(table 4 funcref)
;; Local functions for table
(func $add_func (type $i32_to_i32) (param $x i32) (result i32)
local.get $x
i32.const 10
i32.add)
(func $mul_func (type $i32_to_i32) (param $x i32) (result i32)
local.get $x
i32.const 2
i32.mul)
(func $identity_func (type $i32_to_i32) (param $x i32) (result i32)
local.get $x)
(func $void_func (type $void_to_void))
;; Initialize table
(elem (i32.const 0) $add_func $mul_func $identity_func $void_func)
;; Test functions matching the C++ test expectations
(func (export "test_call_indirect_valid") (param $idx i32) (param $val i32) (result i32)
local.get $val
local.get $idx
call_indirect (type $i32_to_i32))
(func (export "test_call_indirect_invalid_index") (param $val i32) (result i32)
local.get $val
i32.const 10
call_indirect (type $i32_to_i32))
(func (export "test_call_indirect_type_mismatch") (param $val i32) (result i32)
local.get $val
i32.const 3
call_indirect (type $i32_to_i32))
(func (export "test_import_function_call") (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
call $test_import_add)
(func (export "test_import_function_mul") (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
call $test_import_mul)
(func (export "test_native_function_call") (param $a i32) (result i32)
local.get $a
call $native_func)
(func (export "test_malloc_operation") (param $size i32) (result i32)
local.get $size
call $malloc)
(func (export "test_free_operation") (param $ptr i32)
local.get $ptr
call $free)
(func (export "test_malloc_free_cycle") (param $size i32) (result i32)
(local $ptr i32)
local.get $size
call $malloc
local.set $ptr
local.get $ptr
i32.const 42
i32.store
local.get $ptr
i32.load
local.get $ptr
call $free)
(func (export "test_stack_operations") (param $val1 i32) (param $val2 i32) (result i32)
local.get $val1
local.get $val2
call $test_import_add
i32.const 5
call $add_func
i32.add
local.get $val2
i32.const 3
call $test_import_mul
i32.add)
(func (export "test_complex_indirect_calls") (param $selector i32) (param $value i32) (result i32)
local.get $selector
i32.const 0
i32.eq
if (result i32)
local.get $value
i32.const 0
call_indirect (type $i32_to_i32)
else
local.get $selector
i32.const 1
i32.eq
if (result i32)
local.get $value
i32.const 1
call_indirect (type $i32_to_i32)
else
local.get $value
i32.const 2
call_indirect (type $i32_to_i32)
end
end)
(func (export "test_large_param_stack")
(param $p1 i32) (param $p2 i32) (param $p3 i32) (param $p4 i32)
(param $p5 i32) (param $p6 i32) (param $p7 i32) (param $p8 i32)
(result i32)
local.get $p1
local.get $p2
call $test_import_add
local.get $p3
local.get $p4
call $test_import_add
i32.add
local.get $p5
local.get $p6
call $test_import_add
i32.add
local.get $p7
local.get $p8
call $test_import_add
i32.add)
)