| // Copyright 2022 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 <chrono> |
| #include <thread> |
| |
| #include "iree/base/api.h" |
| #include "iree/base/internal/wait_handle.h" |
| #include "iree/testing/gtest.h" |
| #include "iree/testing/status_matchers.h" |
| |
| // NOTE: this file is meant to be included inside of a _test.cc source file. |
| // The file must define these functions to allocate/free the loop. |
| // |out_status| should receive the last global error encountered in the loop. |
| void AllocateLoop(iree_status_t* out_status, iree_allocator_t allocator, |
| iree_loop_t* out_loop); |
| void FreeLoop(iree_allocator_t allocator, iree_loop_t loop); |
| |
| namespace iree { |
| namespace testing { |
| |
| struct LoopTest : public ::testing::Test { |
| iree_allocator_t allocator = iree_allocator_system(); |
| iree_loop_t loop; |
| iree_status_t loop_status = iree_ok_status(); |
| |
| void SetUp() override { |
| IREE_TRACE_SCOPE(); |
| AllocateLoop(&loop_status, allocator, &loop); |
| } |
| void TearDown() override { |
| IREE_TRACE_SCOPE(); |
| FreeLoop(allocator, loop); |
| iree_status_ignore(loop_status); |
| } |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // iree_loop_call |
| //===----------------------------------------------------------------------===// |
| |
| // Tests the simple call interface for running work. |
| TEST_F(LoopTest, Call) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| iree_status_t call_status = iree_status_from_code(IREE_STATUS_DATA_LOSS); |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->call_status = status; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| IREE_ASSERT_OK(user_data.call_status); |
| } |
| |
| // Tests a call that forks into two other calls. |
| TEST_F(LoopTest, CallFork) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| bool called_a = false; |
| bool called_b = false; |
| bool called_c = false; |
| } user_data; |
| |
| // A -> [B, C] |
| IREE_ASSERT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->called_a = true; |
| |
| // B |
| IREE_EXPECT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->called_b = true; |
| return iree_ok_status(); |
| }, |
| user_data)); |
| |
| // C |
| IREE_EXPECT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->called_c = true; |
| return iree_ok_status(); |
| }, |
| user_data)); |
| |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.called_a); |
| EXPECT_TRUE(user_data.called_b); |
| EXPECT_TRUE(user_data.called_c); |
| } |
| |
| // Tests a repeating call - since the loops are intended to be stackless we |
| // should in theory be able to issue calls forever. This test ensures we can do |
| // a really large amount without blowing the native stack. |
| struct CallRepeatedData { |
| int remaining = 2 * 1024; |
| }; |
| static iree_status_t CallRepeatedFn(void* user_data_ptr, iree_loop_t loop, |
| iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<CallRepeatedData*>(user_data_ptr); |
| if (--user_data->remaining) { |
| IREE_RETURN_IF_ERROR(iree_loop_call(loop, IREE_LOOP_PRIORITY_DEFAULT, |
| CallRepeatedFn, user_data)); |
| } |
| return iree_ok_status(); |
| } |
| TEST_F(LoopTest, CallRepeated) { |
| IREE_TRACE_SCOPE(); |
| CallRepeatedData user_data; |
| IREE_ASSERT_OK(iree_loop_call(loop, IREE_LOOP_PRIORITY_DEFAULT, |
| CallRepeatedFn, &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_EQ(user_data.remaining, 0); |
| } |
| |
| // Tests a call that results in failure. |
| TEST_F(LoopTest, CallFailure) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| bool completed = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->completed); |
| user_data->completed = true; |
| return iree_status_from_code(IREE_STATUS_DATA_LOSS); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, loop_status); |
| } |
| |
| // Tests that a failure will abort other pending tasks. |
| TEST_F(LoopTest, CallFailureAborts) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| bool did_call_callback = false; |
| bool did_wait_callback = false; |
| } user_data; |
| |
| // Issue the call that will fail. |
| IREE_ASSERT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->did_call_callback); |
| user_data->did_call_callback = true; |
| return iree_status_from_code(IREE_STATUS_DATA_LOSS); |
| }, |
| &user_data)); |
| |
| // Enqueue a wait that will never complete - if it runs it means we didn't |
| // correctly abort it. |
| IREE_ASSERT_OK(iree_loop_wait_until( |
| loop, iree_make_timeout_ms(1 * 60 * 1000), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_ABORTED, status); |
| iree_status_ignore(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->did_wait_callback); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, loop_status); |
| EXPECT_TRUE(user_data.did_call_callback); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| } |
| |
| // Tests that a failure will abort other pending tasks, including those enqueued |
| // from within the failing call itself. |
| TEST_F(LoopTest, CallFailureAbortsNested) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| bool did_call_callback = false; |
| bool did_wait_callback = false; |
| } user_data; |
| |
| // Issue the call that will fail. |
| IREE_ASSERT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->did_call_callback); |
| user_data->did_call_callback = true; |
| |
| // Enqueue a wait that will never complete - if it runs it means we |
| // didn't correctly abort it. We are enqueuing it reentrantly as a user |
| // would before we encounter the error below. |
| IREE_EXPECT_OK(iree_loop_wait_until( |
| loop, iree_make_timeout_ms(1 * 60 * 1000), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_ABORTED, status); |
| iree_status_ignore(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->did_wait_callback); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| user_data)); |
| |
| return iree_status_from_code(IREE_STATUS_DATA_LOSS); |
| }, |
| &user_data)); |
| |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, loop_status); |
| EXPECT_TRUE(user_data.did_call_callback); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // iree_loop_dispatch |
| //===----------------------------------------------------------------------===// |
| |
| // Tests a grid dispatch operation with an empty grid. |
| // The completion callback should still be issued but no workgroups. |
| TEST_F(LoopTest, DispatchEmpty) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| std::atomic<int> workgroup_count = {0}; |
| bool completed = false; |
| } user_data; |
| const uint32_t xyz[3] = {1, 0, 0}; |
| IREE_ASSERT_OK(iree_loop_dispatch( |
| loop, xyz, |
| +[](void* user_data_ptr, iree_loop_t loop, uint32_t workgroup_x, |
| uint32_t workgroup_y, uint32_t workgroup_z) { |
| IREE_TRACE_SCOPE(); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| ++user_data->workgroup_count; |
| return iree_ok_status(); |
| }, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->completed); |
| user_data->completed = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_EQ(user_data.workgroup_count, 0); |
| EXPECT_TRUE(user_data.completed); |
| } |
| |
| // Tests a grid dispatch operation and ensures all workgroups are issued. |
| TEST_F(LoopTest, DispatchGrid) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| std::atomic<int> workgroup_count = {0}; |
| bool completed = false; |
| } user_data; |
| const uint32_t xyz[3] = {4, 2, 1}; |
| IREE_ASSERT_OK(iree_loop_dispatch( |
| loop, xyz, |
| +[](void* user_data_ptr, iree_loop_t loop, uint32_t workgroup_x, |
| uint32_t workgroup_y, uint32_t workgroup_z) { |
| IREE_TRACE_SCOPE(); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| ++user_data->workgroup_count; |
| return iree_ok_status(); |
| }, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->completed); |
| user_data->completed = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_EQ(user_data.workgroup_count, xyz[0] * xyz[1] * xyz[2]); |
| EXPECT_TRUE(user_data.completed); |
| } |
| |
| // Tests a grid dispatch operation with a workgroup failure. |
| TEST_F(LoopTest, DispatchWorkgroupFailure) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| bool completed = false; |
| } user_data; |
| const uint32_t xyz[3] = {4, 2, 1}; |
| IREE_ASSERT_OK(iree_loop_dispatch( |
| loop, xyz, |
| +[](void* user_data_ptr, iree_loop_t loop, uint32_t workgroup_x, |
| uint32_t workgroup_y, uint32_t workgroup_z) { |
| IREE_TRACE_SCOPE(); |
| return iree_status_from_code(IREE_STATUS_DATA_LOSS); |
| }, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, status); |
| iree_status_ignore(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->completed); |
| user_data->completed = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.completed); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // iree_loop_wait_until |
| //===----------------------------------------------------------------------===// |
| |
| // Tests a wait-until delay with an immediate timeout. |
| TEST_F(LoopTest, WaitUntilImmediate) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| iree_status_t wait_status = iree_status_from_code(IREE_STATUS_DATA_LOSS); |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_until( |
| loop, iree_immediate_timeout(), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->wait_status = status; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| IREE_ASSERT_OK(user_data.wait_status); |
| } |
| |
| // Tests a wait-until delay with an actual delay. |
| TEST_F(LoopTest, WaitUntil) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| iree_time_t start_ns = iree_time_now(); |
| iree_time_t end_ns = IREE_TIME_INFINITE_FUTURE; |
| iree_status_t wait_status = iree_status_from_code(IREE_STATUS_DATA_LOSS); |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_until( |
| loop, iree_make_timeout_ms(50), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->end_ns = iree_time_now(); |
| user_data->wait_status = status; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| IREE_ASSERT_OK(user_data.wait_status); |
| // Not checking exact timing as some devices may not have clocks. |
| EXPECT_GE(user_data.end_ns, user_data.start_ns); |
| } |
| |
| // Tests that multiple wait-until's can be active at once. |
| // NOTE: loops are not required to wake in any particular order. |
| TEST_F(LoopTest, MultiWaitUntil) { |
| IREE_TRACE_SCOPE(); |
| struct UserData { |
| bool woke_a = false; |
| bool woke_b = false; |
| } user_data; |
| |
| IREE_ASSERT_OK(iree_loop_wait_until( |
| loop, iree_make_timeout_ms(25), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->woke_a = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| |
| IREE_ASSERT_OK(iree_loop_wait_until( |
| loop, iree_make_timeout_ms(50), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->woke_b = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.woke_a); |
| EXPECT_TRUE(user_data.woke_b); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // iree_loop_wait_one |
| //===----------------------------------------------------------------------===// |
| |
| // Tests a wait-one with an immediate timeout. |
| // The handle is never resolved and if we didn't bail immediately we'd hang. |
| TEST_F(LoopTest, WaitOneImmediate) { |
| IREE_TRACE_SCOPE(); |
| |
| // An event that never resolves. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_one( |
| loop, wait_source, iree_immediate_timeout(), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event); |
| } |
| |
| // Tests a wait-one with a non-immediate timeout. |
| TEST_F(LoopTest, WaitOneTimeout) { |
| IREE_TRACE_SCOPE(); |
| |
| // An event that never resolves. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_one( |
| loop, wait_source, iree_make_timeout_ms(10), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event); |
| } |
| |
| // Tests a wait-one that times out does not abort other loop ops. |
| // The deadline exceeded status passed to the callback is sufficient. |
| TEST_F(LoopTest, WaitOneTimeoutNoAbort) { |
| IREE_TRACE_SCOPE(); |
| |
| // An event that never resolves. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| struct UserData { |
| bool did_wait_callback = false; |
| bool did_call_callback = false; |
| } user_data; |
| |
| // Wait that will time out. |
| IREE_ASSERT_OK(iree_loop_wait_one( |
| loop, wait_source, iree_make_timeout_ms(10), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| |
| // Call that should still be issued correctly. |
| // Note that we queue it here as if we did it outside the wait we'd |
| // immediately execute it on out-of-order implementations. |
| IREE_EXPECT_OK(iree_loop_call( |
| loop, IREE_LOOP_PRIORITY_DEFAULT, |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| EXPECT_FALSE(user_data->did_call_callback); |
| user_data->did_call_callback = true; |
| return iree_ok_status(); |
| }, |
| user_data)); |
| |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| EXPECT_TRUE(user_data.did_call_callback); |
| |
| iree_event_deinitialize(&event); |
| } |
| |
| // Tests a wait-one with an already signaled wait source. |
| TEST_F(LoopTest, WaitOneSignaled) { |
| IREE_TRACE_SCOPE(); |
| |
| // An event that is resolved immediately. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_one( |
| loop, wait_source, iree_make_timeout_ms(10), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event); |
| } |
| |
| // Tests a wait-one on a wait handle signaled out-of-band. |
| TEST_F(LoopTest, WaitOneBlocking) { |
| IREE_TRACE_SCOPE(); |
| |
| // Initially unsignaled. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| // Spin up the thread to signal the event after a short delay. |
| // We need to do this before we issue the wait so that loops which perform the |
| // wait inline can still make forward progress even if they block. |
| // |
| // Note: there is a race here that can cause flakes. If this new thread |
| // starts running after the iree_loop_wait_* timeout below, the status check |
| // will fail. We make the timeout there sufficiently long to give the OS |
| // enough time to switch threads a few times. |
| std::thread thread([&]() { |
| IREE_TRACE_SCOPE(); |
| std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
| iree_event_set(&event); |
| }); |
| |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_one( |
| loop, wait_source, iree_make_timeout_ms(2000), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| thread.join(); |
| iree_event_deinitialize(&event); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // iree_loop_wait_any |
| //===----------------------------------------------------------------------===// |
| |
| // Tests a wait-any with a immediate timeout (a poll). |
| TEST_F(LoopTest, WaitAnyImmediate) { |
| IREE_TRACE_SCOPE(); |
| |
| // Events that are never resolved such that we time out. |
| iree_event_t event_a; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a)); |
| iree_wait_source_t wait_source_a = iree_event_await(&event_a); |
| iree_event_t event_b; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_b)); |
| iree_wait_source_t wait_source_b = iree_event_await(&event_b); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source_a, |
| wait_source_b, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_any( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_immediate_timeout(), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event_a); |
| iree_event_deinitialize(&event_b); |
| } |
| |
| // Tests a wait-any with a non-immediate timeout. |
| TEST_F(LoopTest, WaitAnyTimeout) { |
| IREE_TRACE_SCOPE(); |
| |
| // Events that are never resolved such that we time out. |
| iree_event_t event_a; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a)); |
| iree_wait_source_t wait_source_a = iree_event_await(&event_a); |
| iree_event_t event_b; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_b)); |
| iree_wait_source_t wait_source_b = iree_event_await(&event_b); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source_a, |
| wait_source_b, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_any( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_make_timeout_ms(10), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event_a); |
| iree_event_deinitialize(&event_b); |
| } |
| |
| // Tests a wait-any with an already-resolved wait handle. |
| TEST_F(LoopTest, WaitAnySignaled) { |
| IREE_TRACE_SCOPE(); |
| |
| // An event that is resolved immediately. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| // Always unsignaled so we test the wait-any behavior. |
| iree_event_t unresolved_event; |
| IREE_ASSERT_OK( |
| iree_event_initialize(/*initial_state=*/false, &unresolved_event)); |
| iree_wait_source_t unresolved_wait_source = |
| iree_event_await(&unresolved_event); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source, |
| unresolved_wait_source, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_any( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_make_timeout_ms(10), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event); |
| iree_event_deinitialize(&unresolved_event); |
| } |
| |
| // Tests a wait-any with a wait handle signaled out-of-band. |
| TEST_F(LoopTest, WaitAnyBlocking) { |
| IREE_TRACE_SCOPE(); |
| |
| // Initially unsignaled. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| // Always unsignaled so we test the wait-any behavior. |
| iree_event_t unresolved_event; |
| IREE_ASSERT_OK( |
| iree_event_initialize(/*initial_state=*/false, &unresolved_event)); |
| iree_wait_source_t unresolved_wait_source = |
| iree_event_await(&unresolved_event); |
| |
| // Spin up the thread to signal the event after a short delay. |
| // We need to do this before we issue the wait so that loops which perform the |
| // wait inline can still make forward progress even if they block. |
| // |
| // Note: there is a race here that can cause flakes. If this new thread |
| // starts running after the iree_loop_wait_* timeout below, the status check |
| // will fail. We make the timeout there sufficiently long to give the OS |
| // enough time to switch threads a few times. |
| std::thread thread([&]() { |
| IREE_TRACE_SCOPE(); |
| std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
| iree_event_set(&event); |
| }); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source, |
| unresolved_wait_source, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_any( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_make_timeout_ms(2000), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| thread.join(); |
| iree_event_deinitialize(&event); |
| iree_event_deinitialize(&unresolved_event); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // iree_loop_wait_all |
| //===----------------------------------------------------------------------===// |
| |
| // Tests a wait-all with a immediate timeout (a poll). |
| TEST_F(LoopTest, WaitAllImmediate) { |
| IREE_TRACE_SCOPE(); |
| |
| // One unresolved and one resolved event (should fail the wait-all). |
| iree_event_t event_a; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a)); |
| iree_wait_source_t wait_source_a = iree_event_await(&event_a); |
| iree_event_t event_b; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_b)); |
| iree_wait_source_t wait_source_b = iree_event_await(&event_b); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source_a, |
| wait_source_b, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_all( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_immediate_timeout(), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event_a); |
| iree_event_deinitialize(&event_b); |
| } |
| |
| // Tests a wait-all with a non-immediate timeout. |
| TEST_F(LoopTest, WaitAllTimeout) { |
| IREE_TRACE_SCOPE(); |
| |
| // One unresolved and one resolved event (should fail the wait-all). |
| iree_event_t event_a; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a)); |
| iree_wait_source_t wait_source_a = iree_event_await(&event_a); |
| iree_event_t event_b; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_b)); |
| iree_wait_source_t wait_source_b = iree_event_await(&event_b); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source_a, |
| wait_source_b, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_all( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_make_timeout_ms(10), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event_a); |
| iree_event_deinitialize(&event_b); |
| } |
| |
| // Tests a wait-all with already-resolved wait handles. |
| TEST_F(LoopTest, WaitAllSignaled) { |
| IREE_TRACE_SCOPE(); |
| |
| // Signaled events so the wait-all succeeds. |
| iree_event_t event_a; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_a)); |
| iree_wait_source_t wait_source_a = iree_event_await(&event_a); |
| iree_event_t event_b; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_b)); |
| iree_wait_source_t wait_source_b = iree_event_await(&event_b); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source_a, |
| wait_source_b, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_all( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_make_timeout_ms(10), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| iree_event_deinitialize(&event_a); |
| iree_event_deinitialize(&event_b); |
| } |
| |
| // Tests a wait-all with wait handles signaled out-of-band. |
| TEST_F(LoopTest, WaitAllBlocking) { |
| IREE_TRACE_SCOPE(); |
| |
| // Initially unsignaled. |
| iree_event_t event; |
| IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event)); |
| iree_wait_source_t wait_source = iree_event_await(&event); |
| |
| // Always unsignaled so we test the wait-any behavior. |
| iree_event_t resolved_event; |
| IREE_ASSERT_OK( |
| iree_event_initialize(/*initial_state=*/true, &resolved_event)); |
| iree_wait_source_t resolved_wait_source = iree_event_await(&resolved_event); |
| |
| // Spin up the thread to signal the event after a short delay. |
| // We need to do this before we issue the wait so that loops which perform the |
| // wait inline can still make forward progress even if they block. |
| // |
| // Note: there is a race here that can cause flakes. If this new thread |
| // starts running after the iree_loop_wait_* timeout below, the status check |
| // will fail. We make the timeout there sufficiently long to give the OS |
| // enough time to switch threads a few times. |
| std::thread thread([&]() { |
| IREE_TRACE_SCOPE(); |
| std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
| iree_event_set(&event); |
| }); |
| |
| iree_wait_source_t wait_sources[2] = { |
| wait_source, |
| resolved_wait_source, |
| }; |
| struct UserData { |
| bool did_wait_callback = false; |
| } user_data; |
| IREE_ASSERT_OK(iree_loop_wait_all( |
| loop, IREE_ARRAYSIZE(wait_sources), wait_sources, |
| iree_make_timeout_ms(2000), |
| +[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) { |
| IREE_TRACE_SCOPE(); |
| IREE_EXPECT_OK(status); |
| auto* user_data = reinterpret_cast<UserData*>(user_data_ptr); |
| user_data->did_wait_callback = true; |
| return iree_ok_status(); |
| }, |
| &user_data)); |
| IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout())); |
| |
| IREE_ASSERT_OK(loop_status); |
| EXPECT_TRUE(user_data.did_wait_callback); |
| |
| thread.join(); |
| iree_event_deinitialize(&event); |
| iree_event_deinitialize(&resolved_event); |
| } |
| |
| } // namespace testing |
| } // namespace iree |