| // Copyright 2019 The IREE Authors |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #include "iree/vm/stack.h" |
| |
| #include "iree/base/api.h" |
| #include "iree/testing/gtest.h" |
| #include "iree/testing/status_matchers.h" |
| |
| namespace { |
| |
| #define MODULE_A_SENTINEL reinterpret_cast<iree_vm_module_t*>(1) |
| #define MODULE_B_SENTINEL reinterpret_cast<iree_vm_module_t*>(2) |
| #define MODULE_A_STATE_SENTINEL reinterpret_cast<iree_vm_module_state_t*>(101) |
| #define MODULE_B_STATE_SENTINEL reinterpret_cast<iree_vm_module_state_t*>(102) |
| |
| static int module_a_state_resolve_count = 0; |
| static int module_b_state_resolve_count = 0; |
| static iree_status_t SentinelStateResolver( |
| void* state_resolver, iree_vm_module_t* module, |
| iree_vm_module_state_t** out_module_state) { |
| if (module == MODULE_A_SENTINEL) { |
| ++module_a_state_resolve_count; |
| *out_module_state = MODULE_A_STATE_SENTINEL; |
| return iree_ok_status(); |
| } else if (module == MODULE_B_SENTINEL) { |
| ++module_b_state_resolve_count; |
| *out_module_state = MODULE_B_STATE_SENTINEL; |
| return iree_ok_status(); |
| } |
| return iree_make_status(IREE_STATUS_NOT_FOUND); |
| } |
| |
| // Tests simple stack usage, mainly just for demonstration. |
| TEST(VMStackTest, Usage) { |
| iree_vm_state_resolver_t state_resolver = {nullptr, SentinelStateResolver}; |
| IREE_VM_INLINE_STACK_INITIALIZE(stack, IREE_VM_INVOCATION_FLAG_NONE, |
| state_resolver, iree_allocator_system()); |
| |
| EXPECT_EQ(nullptr, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_parent_frame(stack)); |
| |
| iree_vm_function_t function_a = {MODULE_A_SENTINEL, |
| IREE_VM_FUNCTION_LINKAGE_INTERNAL, 0}; |
| iree_vm_stack_frame_t* frame_a = nullptr; |
| IREE_EXPECT_OK(iree_vm_stack_function_enter( |
| stack, &function_a, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_a)); |
| EXPECT_EQ(0, frame_a->function.ordinal); |
| EXPECT_EQ(frame_a, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_parent_frame(stack)); |
| |
| iree_vm_function_t function_b = {MODULE_B_SENTINEL, |
| IREE_VM_FUNCTION_LINKAGE_INTERNAL, 1}; |
| iree_vm_stack_frame_t* frame_b = nullptr; |
| IREE_EXPECT_OK(iree_vm_stack_function_enter( |
| stack, &function_b, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_b)); |
| EXPECT_EQ(1, frame_b->function.ordinal); |
| EXPECT_EQ(frame_b, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(frame_a, iree_vm_stack_parent_frame(stack)); |
| |
| IREE_EXPECT_OK(iree_vm_stack_function_leave(stack)); |
| EXPECT_EQ(frame_a, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_parent_frame(stack)); |
| IREE_EXPECT_OK(iree_vm_stack_function_leave(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_parent_frame(stack)); |
| |
| iree_vm_stack_deinitialize(stack); |
| } |
| |
| // Tests stack cleanup with unpopped frames (like during failure teardown). |
| TEST(VMStackTest, DeinitWithRemainingFrames) { |
| iree_vm_state_resolver_t state_resolver = {nullptr, SentinelStateResolver}; |
| IREE_VM_INLINE_STACK_INITIALIZE(stack, IREE_VM_INVOCATION_FLAG_NONE, |
| state_resolver, iree_allocator_system()); |
| |
| iree_vm_function_t function_a = {MODULE_A_SENTINEL, |
| IREE_VM_FUNCTION_LINKAGE_INTERNAL, 0}; |
| iree_vm_stack_frame_t* frame_a = nullptr; |
| IREE_EXPECT_OK(iree_vm_stack_function_enter( |
| stack, &function_a, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_a)); |
| EXPECT_EQ(0, frame_a->function.ordinal); |
| EXPECT_EQ(frame_a, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_parent_frame(stack)); |
| |
| // Don't pop the last frame before deinit; it should handle it. |
| iree_vm_stack_deinitialize(stack); |
| } |
| |
| // Tests stack overflow detection. |
| TEST(VMStackTest, StackOverflow) { |
| iree_vm_state_resolver_t state_resolver = {nullptr, SentinelStateResolver}; |
| IREE_VM_INLINE_STACK_INITIALIZE(stack, IREE_VM_INVOCATION_FLAG_NONE, |
| state_resolver, iree_allocator_system()); |
| |
| EXPECT_EQ(nullptr, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_parent_frame(stack)); |
| |
| // Fill the entire stack up to the max. |
| iree_vm_function_t function_a = {MODULE_A_SENTINEL, |
| IREE_VM_FUNCTION_LINKAGE_INTERNAL, 0}; |
| bool did_overflow = false; |
| for (int i = 0; i < 99999; ++i) { |
| iree_vm_stack_frame_t* frame_a = nullptr; |
| iree_status_t status = iree_vm_stack_function_enter( |
| stack, &function_a, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_a); |
| if (iree_status_is_resource_exhausted(status)) { |
| // Hit the stack overflow, as expected. |
| did_overflow = true; |
| IREE_IGNORE_ERROR(status); |
| break; |
| } |
| IREE_EXPECT_OK(status); |
| } |
| ASSERT_TRUE(did_overflow); |
| |
| iree_vm_stack_deinitialize(stack); |
| } |
| |
| // Tests unbalanced stack popping. |
| TEST(VMStackTest, UnbalancedPop) { |
| iree_vm_state_resolver_t state_resolver = {nullptr, SentinelStateResolver}; |
| IREE_VM_INLINE_STACK_INITIALIZE(stack, IREE_VM_INVOCATION_FLAG_NONE, |
| state_resolver, iree_allocator_system()); |
| |
| iree_status_t status = iree_vm_stack_function_leave(stack); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_FAILED_PRECONDITION, status); |
| iree_status_free(status); |
| |
| iree_vm_stack_deinitialize(stack); |
| } |
| |
| // Tests module state reuse and querying. |
| TEST(VMStackTest, ModuleStateQueries) { |
| iree_vm_state_resolver_t state_resolver = {nullptr, SentinelStateResolver}; |
| IREE_VM_INLINE_STACK_INITIALIZE(stack, IREE_VM_INVOCATION_FLAG_NONE, |
| state_resolver, iree_allocator_system()); |
| |
| EXPECT_EQ(nullptr, iree_vm_stack_current_frame(stack)); |
| EXPECT_EQ(nullptr, iree_vm_stack_parent_frame(stack)); |
| |
| module_a_state_resolve_count = 0; |
| module_b_state_resolve_count = 0; |
| |
| // [A (queried)] |
| iree_vm_function_t function_a = {MODULE_A_SENTINEL, |
| IREE_VM_FUNCTION_LINKAGE_INTERNAL, 0}; |
| iree_vm_stack_frame_t* frame_a = nullptr; |
| IREE_EXPECT_OK(iree_vm_stack_function_enter( |
| stack, &function_a, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_a)); |
| EXPECT_EQ(MODULE_A_STATE_SENTINEL, frame_a->module_state); |
| EXPECT_EQ(1, module_a_state_resolve_count); |
| |
| // [A, B (queried)] |
| iree_vm_function_t function_b = {MODULE_B_SENTINEL, |
| IREE_VM_FUNCTION_LINKAGE_INTERNAL, 1}; |
| iree_vm_stack_frame_t* frame_b = nullptr; |
| IREE_EXPECT_OK(iree_vm_stack_function_enter( |
| stack, &function_b, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_b)); |
| EXPECT_EQ(MODULE_B_STATE_SENTINEL, frame_b->module_state); |
| EXPECT_EQ(1, module_b_state_resolve_count); |
| |
| // [A, B, B (reuse)] |
| IREE_EXPECT_OK(iree_vm_stack_function_enter( |
| stack, &function_b, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_b)); |
| EXPECT_EQ(MODULE_B_STATE_SENTINEL, frame_b->module_state); |
| EXPECT_EQ(1, module_b_state_resolve_count); |
| |
| IREE_EXPECT_OK(iree_vm_stack_function_leave(stack)); |
| IREE_EXPECT_OK(iree_vm_stack_function_leave(stack)); |
| IREE_EXPECT_OK(iree_vm_stack_function_leave(stack)); |
| |
| iree_vm_stack_deinitialize(stack); |
| } |
| |
| // Tests that module state query failures propagate to callers correctly. |
| TEST(VMStackTest, ModuleStateQueryFailure) { |
| iree_vm_state_resolver_t state_resolver = { |
| nullptr, |
| +[](void* state_resolver, iree_vm_module_t* module, |
| iree_vm_module_state_t** out_module_state) -> iree_status_t { |
| // NOTE: always failing. |
| return iree_make_status(IREE_STATUS_INTERNAL); |
| }}; |
| IREE_VM_INLINE_STACK_INITIALIZE(stack, IREE_VM_INVOCATION_FLAG_NONE, |
| state_resolver, iree_allocator_system()); |
| |
| // Push should fail if we can't query state, status should propagate. |
| iree_vm_function_t function_a = {MODULE_A_SENTINEL, |
| IREE_VM_FUNCTION_LINKAGE_INTERNAL, 0}; |
| iree_vm_stack_frame_t* frame_a = nullptr; |
| iree_status_t status = iree_vm_stack_function_enter( |
| stack, &function_a, IREE_VM_STACK_FRAME_NATIVE, 0, NULL, &frame_a); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_INTERNAL, status); |
| iree_status_free(status); |
| iree_vm_stack_deinitialize(stack); |
| } |
| |
| } // namespace |