summaryrefslogtreecommitdiffstats
path: root/runtime/indirect_reference_table.h
blob: 97706b82bb934c97324ff72a7a54a46d1315241f (plain)
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_RUNTIME_INDIRECT_REFERENCE_TABLE_H_
#define ART_RUNTIME_INDIRECT_REFERENCE_TABLE_H_

#include <stdint.h>

#include <iosfwd>
#include <string>

#include "base/logging.h"
#include "offsets.h"
#include "root_visitor.h"

namespace art {
namespace mirror {
class Object;
}  // namespace mirror

/*
 * Maintain a table of indirect references.  Used for local/global JNI
 * references.
 *
 * The table contains object references that are part of the GC root set.
 * When an object is added we return an IndirectRef that is not a valid
 * pointer but can be used to find the original value in O(1) time.
 * Conversions to and from indirect references are performed on upcalls
 * and downcalls, so they need to be very fast.
 *
 * To be efficient for JNI local variable storage, we need to provide
 * operations that allow us to operate on segments of the table, where
 * segments are pushed and popped as if on a stack.  For example, deletion
 * of an entry should only succeed if it appears in the current segment,
 * and we want to be able to strip off the current segment quickly when
 * a method returns.  Additions to the table must be made in the current
 * segment even if space is available in an earlier area.
 *
 * A new segment is created when we call into native code from interpreted
 * code, or when we handle the JNI PushLocalFrame function.
 *
 * The GC must be able to scan the entire table quickly.
 *
 * In summary, these must be very fast:
 *  - adding or removing a segment
 *  - adding references to a new segment
 *  - converting an indirect reference back to an Object
 * These can be a little slower, but must still be pretty quick:
 *  - adding references to a "mature" segment
 *  - removing individual references
 *  - scanning the entire table straight through
 *
 * If there's more than one segment, we don't guarantee that the table
 * will fill completely before we fail due to lack of space.  We do ensure
 * that the current segment will pack tightly, which should satisfy JNI
 * requirements (e.g. EnsureLocalCapacity).
 *
 * To make everything fit nicely in 32-bit integers, the maximum size of
 * the table is capped at 64K.
 *
 * None of the table functions are synchronized.
 */

/*
 * Indirect reference definition.  This must be interchangeable with JNI's
 * jobject, and it's convenient to let null be null, so we use void*.
 *
 * We need a 16-bit table index and a 2-bit reference type (global, local,
 * weak global).  Real object pointers will have zeroes in the low 2 or 3
 * bits (4- or 8-byte alignment), so it's useful to put the ref type
 * in the low bits and reserve zero as an invalid value.
 *
 * The remaining 14 bits can be used to detect stale indirect references.
 * For example, if objects don't move, we can use a hash of the original
 * Object* to make sure the entry hasn't been re-used.  (If the Object*
 * we find there doesn't match because of heap movement, we could do a
 * secondary check on the preserved hash value; this implies that creating
 * a global/local ref queries the hash value and forces it to be saved.)
 *
 * A more rigorous approach would be to put a serial number in the extra
 * bits, and keep a copy of the serial number in a parallel table.  This is
 * easier when objects can move, but requires 2x the memory and additional
 * memory accesses on add/get.  It will catch additional problems, e.g.:
 * create iref1 for obj, delete iref1, create iref2 for same obj, lookup
 * iref1.  A pattern based on object bits will miss this.
 */
typedef void* IndirectRef;

// Magic failure values; must not pass Heap::ValidateObject() or Heap::IsHeapAddress().
static mirror::Object* const kInvalidIndirectRefObject = reinterpret_cast<mirror::Object*>(0xdead4321);
static mirror::Object* const kClearedJniWeakGlobal = reinterpret_cast<mirror::Object*>(0xdead1234);

/*
 * Indirect reference kind, used as the two low bits of IndirectRef.
 *
 * For convenience these match up with enum jobjectRefType from jni.h.
 */
enum IndirectRefKind {
  kSirtOrInvalid = 0,  // <<stack indirect reference table or invalid reference>>
  kLocal         = 1,  // <<local reference>>
  kGlobal        = 2,  // <<global reference>>
  kWeakGlobal    = 3   // <<weak global reference>>
};
std::ostream& operator<<(std::ostream& os, const IndirectRefKind& rhs);

/*
 * Determine what kind of indirect reference this is.
 */
static inline IndirectRefKind GetIndirectRefKind(IndirectRef iref) {
  return static_cast<IndirectRefKind>(reinterpret_cast<uintptr_t>(iref) & 0x03);
}

/*
 * Extended debugging structure.  We keep a parallel array of these, one
 * per slot in the table.
 */
static const size_t kIRTPrevCount = 4;
struct IndirectRefSlot {
  uint32_t serial;
  const mirror::Object* previous[kIRTPrevCount];
};

/* use as initial value for "cookie", and when table has only one segment */
static const uint32_t IRT_FIRST_SEGMENT = 0;

/*
 * Table definition.
 *
 * For the global reference table, the expected common operations are
 * adding a new entry and removing a recently-added entry (usually the
 * most-recently-added entry).  For JNI local references, the common
 * operations are adding a new entry and removing an entire table segment.
 *
 * If "alloc_entries_" is not equal to "max_entries_", the table may expand
 * when entries are added, which means the memory may move.  If you want
 * to keep pointers into "table" rather than offsets, you must use a
 * fixed-size table.
 *
 * If we delete entries from the middle of the list, we will be left with
 * "holes".  We track the number of holes so that, when adding new elements,
 * we can quickly decide to do a trivial append or go slot-hunting.
 *
 * When the top-most entry is removed, any holes immediately below it are
 * also removed.  Thus, deletion of an entry may reduce "topIndex" by more
 * than one.
 *
 * To get the desired behavior for JNI locals, we need to know the bottom
 * and top of the current "segment".  The top is managed internally, and
 * the bottom is passed in as a function argument.  When we call a native method or
 * push a local frame, the current top index gets pushed on, and serves
 * as the new bottom.  When we pop a frame off, the value from the stack
 * becomes the new top index, and the value stored in the previous frame
 * becomes the new bottom.
 *
 * To avoid having to re-scan the table after a pop, we want to push the
 * number of holes in the table onto the stack.  Because of our 64K-entry
 * cap, we can combine the two into a single unsigned 32-bit value.
 * Instead of a "bottom" argument we take a "cookie", which includes the
 * bottom index and the count of holes below the bottom.
 *
 * Common alternative implementation: make IndirectRef a pointer to the
 * actual reference slot.  Instead of getting a table and doing a lookup,
 * the lookup can be done instantly.  Operations like determining the
 * type and deleting the reference are more expensive because the table
 * must be hunted for (i.e. you have to do a pointer comparison to see
 * which table it's in), you can't move the table when expanding it (so
 * realloc() is out), and tricks like serial number checking to detect
 * stale references aren't possible (though we may be able to get similar
 * benefits with other approaches).
 *
 * TODO: consider a "lastDeleteIndex" for quick hole-filling when an
 * add immediately follows a delete; must invalidate after segment pop
 * (which could increase the cost/complexity of method call/return).
 * Might be worth only using it for JNI globals.
 *
 * TODO: may want completely different add/remove algorithms for global
 * and local refs to improve performance.  A large circular buffer might
 * reduce the amortized cost of adding global references.
 *
 * TODO: if we can guarantee that the underlying storage doesn't move,
 * e.g. by using oversized mmap regions to handle expanding tables, we may
 * be able to avoid having to synchronize lookups.  Might make sense to
 * add a "synchronized lookup" call that takes the mutex as an argument,
 * and either locks or doesn't lock based on internal details.
 */
union IRTSegmentState {
  uint32_t          all;
  struct {
    uint32_t      topIndex:16;            /* index of first unused entry */
    uint32_t      numHoles:16;            /* #of holes in entire table */
  } parts;
};

class IrtIterator {
 public:
  explicit IrtIterator(const mirror::Object** table, size_t i, size_t capacity)
      : table_(table), i_(i), capacity_(capacity) {
    SkipNullsAndTombstones();
  }

  IrtIterator& operator++() {
    ++i_;
    SkipNullsAndTombstones();
    return *this;
  }

  const mirror::Object** operator*() {
    return &table_[i_];
  }

  bool equals(const IrtIterator& rhs) const {
    return (i_ == rhs.i_ && table_ == rhs.table_);
  }

 private:
  void SkipNullsAndTombstones() {
    // We skip NULLs and tombstones. Clients don't want to see implementation details.
    while (i_ < capacity_ && (table_[i_] == NULL || table_[i_] == kClearedJniWeakGlobal)) {
      ++i_;
    }
  }

  const mirror::Object** table_;
  size_t i_;
  size_t capacity_;
};

bool inline operator==(const IrtIterator& lhs, const IrtIterator& rhs) {
  return lhs.equals(rhs);
}

bool inline operator!=(const IrtIterator& lhs, const IrtIterator& rhs) {
  return !lhs.equals(rhs);
}

class IndirectReferenceTable {
 public:
  typedef IrtIterator iterator;

  IndirectReferenceTable(size_t initialCount, size_t maxCount, IndirectRefKind kind);

  ~IndirectReferenceTable();

  /*
   * Add a new entry.  "obj" must be a valid non-NULL object reference.
   *
   * Returns NULL if the table is full (max entries reached, or alloc
   * failed during expansion).
   */
  IndirectRef Add(uint32_t cookie, const mirror::Object* obj)
      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);

  /*
   * Given an IndirectRef in the table, return the Object it refers to.
   *
   * Returns kInvalidIndirectRefObject if iref is invalid.
   */
  const mirror::Object* Get(IndirectRef iref) const {
    if (!GetChecked(iref)) {
      return kInvalidIndirectRefObject;
    }
    return table_[ExtractIndex(iref)];
  }

  // TODO: remove when we remove work_around_app_jni_bugs support.
  bool ContainsDirectPointer(mirror::Object* direct_pointer) const;

  /*
   * Remove an existing entry.
   *
   * If the entry is not between the current top index and the bottom index
   * specified by the cookie, we don't remove anything.  This is the behavior
   * required by JNI's DeleteLocalRef function.
   *
   * Returns "false" if nothing was removed.
   */
  bool Remove(uint32_t cookie, IndirectRef iref);

  void AssertEmpty();

  void Dump(std::ostream& os) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);

  /*
   * Return the #of entries in the entire table.  This includes holes, and
   * so may be larger than the actual number of "live" entries.
   */
  size_t Capacity() const {
    return segment_state_.parts.topIndex;
  }

  iterator begin() {
    return iterator(table_, 0, Capacity());
  }

  iterator end() {
    return iterator(table_, Capacity(), Capacity());
  }

  void VisitRoots(RootVisitor* visitor, void* arg);

  uint32_t GetSegmentState() const {
    return segment_state_.all;
  }

  void SetSegmentState(uint32_t new_state) {
    segment_state_.all = new_state;
  }

  static Offset SegmentStateOffset() {
    return Offset(OFFSETOF_MEMBER(IndirectReferenceTable, segment_state_));
  }

 private:
  /*
   * Extract the table index from an indirect reference.
   */
  static uint32_t ExtractIndex(IndirectRef iref) {
    uint32_t uref = (uint32_t) iref;
    return (uref >> 2) & 0xffff;
  }

  /*
   * The object pointer itself is subject to relocation in some GC
   * implementations, so we shouldn't really be using it here.
   */
  IndirectRef ToIndirectRef(const mirror::Object* /*o*/, uint32_t tableIndex) const {
    DCHECK_LT(tableIndex, 65536U);
    uint32_t serialChunk = slot_data_[tableIndex].serial;
    uint32_t uref = serialChunk << 20 | (tableIndex << 2) | kind_;
    return (IndirectRef) uref;
  }

  /*
   * Update extended debug info when an entry is added.
   *
   * We advance the serial number, invalidating any outstanding references to
   * this slot.
   */
  void UpdateSlotAdd(const mirror::Object* obj, int slot) {
    if (slot_data_ != NULL) {
      IndirectRefSlot* pSlot = &slot_data_[slot];
      pSlot->serial++;
      pSlot->previous[pSlot->serial % kIRTPrevCount] = obj;
    }
  }

  /* extra debugging checks */
  bool GetChecked(IndirectRef) const;
  bool CheckEntry(const char*, IndirectRef, int) const;

  /* semi-public - read/write by jni down calls */
  IRTSegmentState segment_state_;

  /* bottom of the stack */
  const mirror::Object** table_;
  /* bit mask, ORed into all irefs */
  IndirectRefKind kind_;
  /* extended debugging info */
  IndirectRefSlot* slot_data_;
  /* #of entries we have space for */
  size_t alloc_entries_;
  /* max #of entries allowed */
  size_t max_entries_;
};

}  // namespace art

#endif  // ART_RUNTIME_INDIRECT_REFERENCE_TABLE_H_