forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathengine.cpp
153 lines (135 loc) · 5.35 KB
/
engine.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// This file is a part of Julia. License is MIT: https://julialang.org/license
#include <mutex>
#include <condition_variable>
#include <llvm/ADT/DenseMap.h>
#include <llvm/ADT/DenseSet.h>
#include <llvm/ADT/SmallVector.h>
#include "julia.h"
#include "julia_internal.h"
#include "julia_assert.h"
using namespace llvm;
struct ReservationInfo {
int16_t tid = 0;
jl_code_instance_t *ci = nullptr;
};
struct InferKey {
jl_method_instance_t *mi = nullptr;
jl_value_t *owner = nullptr;
};
template<> struct llvm::DenseMapInfo<InferKey> {
using FirstInfo = DenseMapInfo<jl_method_instance_t*>;
using SecondInfo = DenseMapInfo<jl_value_t*>;
static inline InferKey getEmptyKey() {
return InferKey{FirstInfo::getEmptyKey(),
SecondInfo::getEmptyKey()};
}
static inline InferKey getTombstoneKey() {
return InferKey{FirstInfo::getTombstoneKey(),
SecondInfo::getTombstoneKey()};
}
static unsigned getHashValue(const InferKey& PairVal) {
return detail::combineHashValue(FirstInfo::getHashValue(PairVal.mi),
SecondInfo::getHashValue(PairVal.owner));
}
static bool isEqual(const InferKey &LHS, const InferKey &RHS) {
return LHS.mi == RHS.mi && LHS.owner == RHS.owner;
}
};
static std::mutex engine_lock; // n.b. this lock is only ever held briefly
static std::condition_variable engine_wait; // but it may be waiting a while in this state
// map from MethodInstance to threadid that owns it currently for inference
static DenseMap<InferKey, ReservationInfo> Reservations;
// vector of which threads are blocked and which lease they need
static SmallVector<InferKey, 0> Awaiting; // (this could be merged into ptls also)
#ifdef __cplusplus
extern "C" {
#endif
jl_code_instance_t *jl_engine_reserve(jl_method_instance_t *m, jl_value_t *owner)
{
jl_task_t *ct = jl_current_task;
ct->ptls->engine_nqueued++; // disables finalizers until inference is finished on this method graph
jl_code_instance_t *ci = jl_new_codeinst_uninit(m, owner); // allocate a placeholder
JL_GC_PUSH1(&ci);
auto tid = jl_atomic_load_relaxed(&ct->tid);
if (([tid, m, owner, ci] () -> bool { // necessary scope block / lambda for unique_lock
jl_unique_gcsafe_lock lock(engine_lock);
InferKey key{m, owner};
if ((signed)Awaiting.size() < tid + 1)
Awaiting.resize(tid + 1);
while (1) {
auto record = Reservations.find(key);
if (record == Reservations.end()) {
Reservations[key] = ReservationInfo{tid, ci};
return false;
}
// before waiting, need to run deadlock/cycle detection
// there is a cycle if the thread holding our lease is blocked
// and waiting for (transitively) any lease that is held by this thread
auto wait_tid = record->second.tid;
while (1) {
if (wait_tid == tid)
return true;
if ((signed)Awaiting.size() <= wait_tid)
break; // no cycle, since it is running (and this should be unreachable)
auto key2 = Awaiting[wait_tid];
if (key2.mi == nullptr)
break; // no cycle, since it is running
auto record2 = Reservations.find(key2);
if (record2 == Reservations.end())
break; // no cycle, since it is about to resume
assert(wait_tid != record2->second.tid);
wait_tid = record2->second.tid;
}
Awaiting[tid] = key;
lock.wait(engine_wait);
Awaiting[tid] = InferKey{};
}
})())
ct->ptls->engine_nqueued--;
JL_GC_POP();
return ci;
}
int jl_engine_hasreserved(jl_method_instance_t *m, jl_value_t *owner)
{
jl_task_t *ct = jl_current_task;
InferKey key = {m, owner};
std::unique_lock lock(engine_lock);
auto record = Reservations.find(key);
return record != Reservations.end() && record->second.tid == jl_atomic_load_relaxed(&ct->tid);
}
STATIC_INLINE int gc_marked(uintptr_t bits) JL_NOTSAFEPOINT
{
return (bits & GC_MARKED) != 0;
}
void jl_engine_sweep(jl_ptls_t *gc_all_tls_states)
{
std::unique_lock lock(engine_lock);
bool any = false;
for (auto I = Reservations.begin(); I != Reservations.end(); ++I) {
jl_code_instance_t *ci = I->second.ci;
if (!gc_marked(jl_astaggedvalue(ci)->bits.gc)) {
auto tid = I->second.tid;
Reservations.erase(I);
jl_ptls_t ptls2 = gc_all_tls_states[tid];
ptls2->engine_nqueued--;
any = true;
}
}
if (any)
engine_wait.notify_all();
}
void jl_engine_fulfill(jl_code_instance_t *ci, jl_code_info_t *src)
{
jl_task_t *ct = jl_current_task;
std::unique_lock lock(engine_lock);
auto record = Reservations.find(InferKey{jl_get_ci_mi(ci), ci->owner});
if (record == Reservations.end() || record->second.ci != ci)
return;
assert(jl_atomic_load_relaxed(&ct->tid) == record->second.tid);
ct->ptls->engine_nqueued--; // re-enables finalizers, but doesn't immediately try to run them
Reservations.erase(record);
engine_wait.notify_all();
}
#ifdef __cplusplus
}
#endif