/src/WasmEdge/include/runtime/instance/reflifetime.h
Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 |
2 | | // SPDX-FileCopyrightText: 2019-2026 Second State INC |
3 | | |
4 | | //===-- wasmedge/runtime/instance/reflifetime.h - RefLifetime def ---------===// |
5 | | // |
6 | | // Part of the WasmEdge Project. |
7 | | // |
8 | | //===----------------------------------------------------------------------===// |
9 | | /// |
10 | | /// \file |
11 | | /// This file defines RefLifetime, the intrusive lifetime counter used to decide |
12 | | /// when a heap-allocated, dependency-shared instance may delete itself. |
13 | | /// |
14 | | //===----------------------------------------------------------------------===// |
15 | | #pragma once |
16 | | |
17 | | #include "common/errcode.h" |
18 | | |
19 | | #include <atomic> |
20 | | #include <cstdint> |
21 | | |
22 | | namespace WasmEdge { |
23 | | namespace Runtime { |
24 | | namespace Instance { |
25 | | |
26 | | /// Intrusive lifetime counter for a heap object shared by importers. |
27 | | /// |
28 | | /// Packs an owner flag and a dependent count into one atomic so the deletion |
29 | | /// decision is a single read-modify-write. Defaults to owner-held with zero |
30 | | /// dependents, so an object that never calls releaseOwner() is never |
31 | | /// self-deleted. |
32 | | class RefLifetime { |
33 | | public: |
34 | | /// Pin this object for one importer. The caller must keep the object alive |
35 | | /// across the call (via an existing pin or a store lock spanning lookup and |
36 | | /// pin). |
37 | 0 | void addDependent() noexcept { |
38 | 0 | Bits.fetch_add(DependentUnit, std::memory_order_relaxed); |
39 | 0 | } |
40 | | |
41 | | /// Release one importer pin. Returns true iff the caller must delete the |
42 | | /// object now (last dependent gone after the owner already released). An |
43 | | /// over-release trips assuming() in debug and is a no-op in release. |
44 | 0 | [[nodiscard]] bool releaseDependent() noexcept { |
45 | 0 | uint64_t Prev = Bits.load(std::memory_order_acquire); |
46 | 0 | while (true) { |
47 | 0 | assuming((Prev & CountMask) != 0U); |
48 | 0 | if ((Prev & CountMask) == 0U) { |
49 | 0 | return false; |
50 | 0 | } |
51 | 0 | if (Bits.compare_exchange_weak(Prev, Prev - DependentUnit, |
52 | 0 | std::memory_order_acq_rel, |
53 | 0 | std::memory_order_relaxed)) { |
54 | 0 | return Prev == DependentUnit; |
55 | 0 | } |
56 | 0 | } |
57 | 0 | } |
58 | | |
59 | | /// Relinquish external ownership, opting a heap instance into deferred |
60 | | /// deletion. Idempotent: a second call is a no-op and never double-deletes. |
61 | | /// Returns true iff the caller must delete now, i.e. no dependents remain. |
62 | 0 | [[nodiscard]] bool releaseOwner() noexcept { |
63 | 0 | const uint64_t Prev = Bits.fetch_and(~OwnerBit, std::memory_order_acq_rel); |
64 | 0 | return Prev == OwnerBit; |
65 | 0 | } |
66 | | |
67 | | /// True iff at least one importer still pins this object. |
68 | 0 | [[nodiscard]] bool hasDependents() const noexcept { |
69 | 0 | return (Bits.load(std::memory_order_acquire) & CountMask) != 0U; |
70 | 0 | } |
71 | | |
72 | | private: |
73 | | static constexpr uint64_t OwnerBit = UINT64_C(1) << 32; |
74 | | static constexpr uint64_t CountMask = OwnerBit - 1U; |
75 | | static constexpr uint64_t DependentUnit = UINT64_C(1); |
76 | | |
77 | | std::atomic<uint64_t> Bits{OwnerBit}; |
78 | | }; |
79 | | |
80 | | } // namespace Instance |
81 | | } // namespace Runtime |
82 | | } // namespace WasmEdge |