Coverage Report

Created: 2026-06-30 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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