• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "transaction.h"
18 
19 #include "base/stl_util.h"
20 #include "base/logging.h"
21 #include "gc/accounting/card_table-inl.h"
22 #include "intern_table.h"
23 #include "mirror/class-inl.h"
24 #include "mirror/object-inl.h"
25 #include "mirror/object_array-inl.h"
26 
27 #include <list>
28 
29 namespace art {
30 
31 // TODO: remove (only used for debugging purpose).
32 static constexpr bool kEnableTransactionStats = false;
33 
Transaction()34 Transaction::Transaction()
35   : log_lock_("transaction log lock", kTransactionLogLock), aborted_(false) {
36   CHECK(Runtime::Current()->IsAotCompiler());
37 }
38 
~Transaction()39 Transaction::~Transaction() {
40   if (kEnableTransactionStats) {
41     MutexLock mu(Thread::Current(), log_lock_);
42     size_t objects_count = object_logs_.size();
43     size_t field_values_count = 0;
44     for (auto it : object_logs_) {
45       field_values_count += it.second.Size();
46     }
47     size_t array_count = array_logs_.size();
48     size_t array_values_count = 0;
49     for (auto it : array_logs_) {
50       array_values_count += it.second.Size();
51     }
52     size_t string_count = intern_string_logs_.size();
53     LOG(INFO) << "Transaction::~Transaction"
54               << ": objects_count=" << objects_count
55               << ", field_values_count=" << field_values_count
56               << ", array_count=" << array_count
57               << ", array_values_count=" << array_values_count
58               << ", string_count=" << string_count;
59   }
60 }
61 
Abort(const std::string & abort_message)62 void Transaction::Abort(const std::string& abort_message) {
63   MutexLock mu(Thread::Current(), log_lock_);
64   // We may abort more than once if the exception thrown at the time of the
65   // previous abort has been caught during execution of a class initializer.
66   // We just keep the message of the first abort because it will cause the
67   // transaction to be rolled back anyway.
68   if (!aborted_) {
69     aborted_ = true;
70     abort_message_ = abort_message;
71   }
72 }
73 
ThrowAbortError(Thread * self,const std::string * abort_message)74 void Transaction::ThrowAbortError(Thread* self, const std::string* abort_message) {
75   const bool rethrow = (abort_message == nullptr);
76   if (kIsDebugBuild && rethrow) {
77     CHECK(IsAborted()) << "Rethrow " << Transaction::kAbortExceptionDescriptor
78                        << " while transaction is not aborted";
79   }
80   if (rethrow) {
81     // Rethrow an exception with the earlier abort message stored in the transaction.
82     self->ThrowNewWrappedException(Transaction::kAbortExceptionSignature,
83                                    GetAbortMessage().c_str());
84   } else {
85     // Throw an exception with the given abort message.
86     self->ThrowNewWrappedException(Transaction::kAbortExceptionSignature,
87                                    abort_message->c_str());
88   }
89 }
90 
IsAborted()91 bool Transaction::IsAborted() {
92   MutexLock mu(Thread::Current(), log_lock_);
93   return aborted_;
94 }
95 
GetAbortMessage()96 const std::string& Transaction::GetAbortMessage() {
97   MutexLock mu(Thread::Current(), log_lock_);
98   return abort_message_;
99 }
100 
RecordWriteFieldBoolean(mirror::Object * obj,MemberOffset field_offset,uint8_t value,bool is_volatile)101 void Transaction::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset,
102                                           uint8_t value, bool is_volatile) {
103   DCHECK(obj != nullptr);
104   MutexLock mu(Thread::Current(), log_lock_);
105   ObjectLog& object_log = object_logs_[obj];
106   object_log.LogBooleanValue(field_offset, value, is_volatile);
107 }
108 
RecordWriteFieldByte(mirror::Object * obj,MemberOffset field_offset,int8_t value,bool is_volatile)109 void Transaction::RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset,
110                                        int8_t value, bool is_volatile) {
111   DCHECK(obj != nullptr);
112   MutexLock mu(Thread::Current(), log_lock_);
113   ObjectLog& object_log = object_logs_[obj];
114   object_log.LogByteValue(field_offset, value, is_volatile);
115 }
116 
RecordWriteFieldChar(mirror::Object * obj,MemberOffset field_offset,uint16_t value,bool is_volatile)117 void Transaction::RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset,
118                                        uint16_t value, bool is_volatile) {
119   DCHECK(obj != nullptr);
120   MutexLock mu(Thread::Current(), log_lock_);
121   ObjectLog& object_log = object_logs_[obj];
122   object_log.LogCharValue(field_offset, value, is_volatile);
123 }
124 
125 
RecordWriteFieldShort(mirror::Object * obj,MemberOffset field_offset,int16_t value,bool is_volatile)126 void Transaction::RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset,
127                                         int16_t value, bool is_volatile) {
128   DCHECK(obj != nullptr);
129   MutexLock mu(Thread::Current(), log_lock_);
130   ObjectLog& object_log = object_logs_[obj];
131   object_log.LogShortValue(field_offset, value, is_volatile);
132 }
133 
134 
RecordWriteField32(mirror::Object * obj,MemberOffset field_offset,uint32_t value,bool is_volatile)135 void Transaction::RecordWriteField32(mirror::Object* obj, MemberOffset field_offset, uint32_t value,
136                                      bool is_volatile) {
137   DCHECK(obj != nullptr);
138   MutexLock mu(Thread::Current(), log_lock_);
139   ObjectLog& object_log = object_logs_[obj];
140   object_log.Log32BitsValue(field_offset, value, is_volatile);
141 }
142 
RecordWriteField64(mirror::Object * obj,MemberOffset field_offset,uint64_t value,bool is_volatile)143 void Transaction::RecordWriteField64(mirror::Object* obj, MemberOffset field_offset, uint64_t value,
144                                      bool is_volatile) {
145   DCHECK(obj != nullptr);
146   MutexLock mu(Thread::Current(), log_lock_);
147   ObjectLog& object_log = object_logs_[obj];
148   object_log.Log64BitsValue(field_offset, value, is_volatile);
149 }
150 
RecordWriteFieldReference(mirror::Object * obj,MemberOffset field_offset,mirror::Object * value,bool is_volatile)151 void Transaction::RecordWriteFieldReference(mirror::Object* obj, MemberOffset field_offset,
152                                             mirror::Object* value, bool is_volatile) {
153   DCHECK(obj != nullptr);
154   MutexLock mu(Thread::Current(), log_lock_);
155   ObjectLog& object_log = object_logs_[obj];
156   object_log.LogReferenceValue(field_offset, value, is_volatile);
157 }
158 
RecordWriteArray(mirror::Array * array,size_t index,uint64_t value)159 void Transaction::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) {
160   DCHECK(array != nullptr);
161   DCHECK(array->IsArrayInstance());
162   DCHECK(!array->IsObjectArray());
163   MutexLock mu(Thread::Current(), log_lock_);
164   ArrayLog& array_log = array_logs_[array];
165   array_log.LogValue(index, value);
166 }
167 
RecordStrongStringInsertion(mirror::String * s)168 void Transaction::RecordStrongStringInsertion(mirror::String* s) {
169   InternStringLog log(s, InternStringLog::kStrongString, InternStringLog::kInsert);
170   LogInternedString(log);
171 }
172 
RecordWeakStringInsertion(mirror::String * s)173 void Transaction::RecordWeakStringInsertion(mirror::String* s) {
174   InternStringLog log(s, InternStringLog::kWeakString, InternStringLog::kInsert);
175   LogInternedString(log);
176 }
177 
RecordStrongStringRemoval(mirror::String * s)178 void Transaction::RecordStrongStringRemoval(mirror::String* s) {
179   InternStringLog log(s, InternStringLog::kStrongString, InternStringLog::kRemove);
180   LogInternedString(log);
181 }
182 
RecordWeakStringRemoval(mirror::String * s)183 void Transaction::RecordWeakStringRemoval(mirror::String* s) {
184   InternStringLog log(s, InternStringLog::kWeakString, InternStringLog::kRemove);
185   LogInternedString(log);
186 }
187 
LogInternedString(const InternStringLog & log)188 void Transaction::LogInternedString(const InternStringLog& log) {
189   Locks::intern_table_lock_->AssertExclusiveHeld(Thread::Current());
190   MutexLock mu(Thread::Current(), log_lock_);
191   intern_string_logs_.push_front(log);
192 }
193 
Rollback()194 void Transaction::Rollback() {
195   CHECK(!Runtime::Current()->IsActiveTransaction());
196   Thread* self = Thread::Current();
197   self->AssertNoPendingException();
198   MutexLock mu1(self, *Locks::intern_table_lock_);
199   MutexLock mu2(self, log_lock_);
200   UndoObjectModifications();
201   UndoArrayModifications();
202   UndoInternStringTableModifications();
203 }
204 
UndoObjectModifications()205 void Transaction::UndoObjectModifications() {
206   // TODO we may not need to restore objects allocated during this transaction. Or we could directly
207   // remove them from the heap.
208   for (auto it : object_logs_) {
209     it.second.Undo(it.first);
210   }
211   object_logs_.clear();
212 }
213 
UndoArrayModifications()214 void Transaction::UndoArrayModifications() {
215   // TODO we may not need to restore array allocated during this transaction. Or we could directly
216   // remove them from the heap.
217   for (auto it : array_logs_) {
218     it.second.Undo(it.first);
219   }
220   array_logs_.clear();
221 }
222 
UndoInternStringTableModifications()223 void Transaction::UndoInternStringTableModifications() {
224   InternTable* const intern_table = Runtime::Current()->GetInternTable();
225   // We want to undo each operation from the most recent to the oldest. List has been filled so the
226   // most recent operation is at list begin so just have to iterate over it.
227   for (InternStringLog& string_log : intern_string_logs_) {
228     string_log.Undo(intern_table);
229   }
230   intern_string_logs_.clear();
231 }
232 
VisitRoots(RootVisitor * visitor)233 void Transaction::VisitRoots(RootVisitor* visitor) {
234   MutexLock mu(Thread::Current(), log_lock_);
235   VisitObjectLogs(visitor);
236   VisitArrayLogs(visitor);
237   VisitStringLogs(visitor);
238 }
239 
VisitObjectLogs(RootVisitor * visitor)240 void Transaction::VisitObjectLogs(RootVisitor* visitor) {
241   // List of moving roots.
242   typedef std::pair<mirror::Object*, mirror::Object*> ObjectPair;
243   std::list<ObjectPair> moving_roots;
244 
245   // Visit roots.
246   for (auto it : object_logs_) {
247     it.second.VisitRoots(visitor);
248     mirror::Object* old_root = it.first;
249     mirror::Object* new_root = old_root;
250     visitor->VisitRoot(&new_root, RootInfo(kRootUnknown));
251     if (new_root != old_root) {
252       moving_roots.push_back(std::make_pair(old_root, new_root));
253     }
254   }
255 
256   // Update object logs with moving roots.
257   for (const ObjectPair& pair : moving_roots) {
258     mirror::Object* old_root = pair.first;
259     mirror::Object* new_root = pair.second;
260     auto old_root_it = object_logs_.find(old_root);
261     CHECK(old_root_it != object_logs_.end());
262     CHECK(object_logs_.find(new_root) == object_logs_.end());
263     object_logs_.insert(std::make_pair(new_root, old_root_it->second));
264     object_logs_.erase(old_root_it);
265   }
266 }
267 
VisitArrayLogs(RootVisitor * visitor)268 void Transaction::VisitArrayLogs(RootVisitor* visitor) {
269   // List of moving roots.
270   typedef std::pair<mirror::Array*, mirror::Array*> ArrayPair;
271   std::list<ArrayPair> moving_roots;
272 
273   for (auto it : array_logs_) {
274     mirror::Array* old_root = it.first;
275     CHECK(!old_root->IsObjectArray());
276     mirror::Array* new_root = old_root;
277     visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&new_root), RootInfo(kRootUnknown));
278     if (new_root != old_root) {
279       moving_roots.push_back(std::make_pair(old_root, new_root));
280     }
281   }
282 
283   // Update array logs with moving roots.
284   for (const ArrayPair& pair : moving_roots) {
285     mirror::Array* old_root = pair.first;
286     mirror::Array* new_root = pair.second;
287     auto old_root_it = array_logs_.find(old_root);
288     CHECK(old_root_it != array_logs_.end());
289     CHECK(array_logs_.find(new_root) == array_logs_.end());
290     array_logs_.insert(std::make_pair(new_root, old_root_it->second));
291     array_logs_.erase(old_root_it);
292   }
293 }
294 
VisitStringLogs(RootVisitor * visitor)295 void Transaction::VisitStringLogs(RootVisitor* visitor) {
296   for (InternStringLog& log : intern_string_logs_) {
297     log.VisitRoots(visitor);
298   }
299 }
300 
LogBooleanValue(MemberOffset offset,uint8_t value,bool is_volatile)301 void Transaction::ObjectLog::LogBooleanValue(MemberOffset offset, uint8_t value, bool is_volatile) {
302   LogValue(ObjectLog::kBoolean, offset, value, is_volatile);
303 }
304 
LogByteValue(MemberOffset offset,int8_t value,bool is_volatile)305 void Transaction::ObjectLog::LogByteValue(MemberOffset offset, int8_t value, bool is_volatile) {
306   LogValue(ObjectLog::kByte, offset, value, is_volatile);
307 }
308 
LogCharValue(MemberOffset offset,uint16_t value,bool is_volatile)309 void Transaction::ObjectLog::LogCharValue(MemberOffset offset, uint16_t value, bool is_volatile) {
310   LogValue(ObjectLog::kChar, offset, value, is_volatile);
311 }
312 
LogShortValue(MemberOffset offset,int16_t value,bool is_volatile)313 void Transaction::ObjectLog::LogShortValue(MemberOffset offset, int16_t value, bool is_volatile) {
314   LogValue(ObjectLog::kShort, offset, value, is_volatile);
315 }
316 
Log32BitsValue(MemberOffset offset,uint32_t value,bool is_volatile)317 void Transaction::ObjectLog::Log32BitsValue(MemberOffset offset, uint32_t value, bool is_volatile) {
318   LogValue(ObjectLog::k32Bits, offset, value, is_volatile);
319 }
320 
Log64BitsValue(MemberOffset offset,uint64_t value,bool is_volatile)321 void Transaction::ObjectLog::Log64BitsValue(MemberOffset offset, uint64_t value, bool is_volatile) {
322   LogValue(ObjectLog::k64Bits, offset, value, is_volatile);
323 }
324 
LogReferenceValue(MemberOffset offset,mirror::Object * obj,bool is_volatile)325 void Transaction::ObjectLog::LogReferenceValue(MemberOffset offset, mirror::Object* obj, bool is_volatile) {
326   LogValue(ObjectLog::kReference, offset, reinterpret_cast<uintptr_t>(obj), is_volatile);
327 }
328 
LogValue(ObjectLog::FieldValueKind kind,MemberOffset offset,uint64_t value,bool is_volatile)329 void Transaction::ObjectLog::LogValue(ObjectLog::FieldValueKind kind,
330                                       MemberOffset offset, uint64_t value, bool is_volatile) {
331   auto it = field_values_.find(offset.Uint32Value());
332   if (it == field_values_.end()) {
333     ObjectLog::FieldValue field_value;
334     field_value.value = value;
335     field_value.is_volatile = is_volatile;
336     field_value.kind = kind;
337     field_values_.insert(std::make_pair(offset.Uint32Value(), field_value));
338   }
339 }
340 
Undo(mirror::Object * obj)341 void Transaction::ObjectLog::Undo(mirror::Object* obj) {
342   for (auto& it : field_values_) {
343     // Garbage collector needs to access object's class and array's length. So we don't rollback
344     // these values.
345     MemberOffset field_offset(it.first);
346     if (field_offset.Uint32Value() == mirror::Class::ClassOffset().Uint32Value()) {
347       // Skip Object::class field.
348       continue;
349     }
350     if (obj->IsArrayInstance() &&
351         field_offset.Uint32Value() == mirror::Array::LengthOffset().Uint32Value()) {
352       // Skip Array::length field.
353       continue;
354     }
355     FieldValue& field_value = it.second;
356     UndoFieldWrite(obj, field_offset, field_value);
357   }
358 }
359 
UndoFieldWrite(mirror::Object * obj,MemberOffset field_offset,const FieldValue & field_value)360 void Transaction::ObjectLog::UndoFieldWrite(mirror::Object* obj, MemberOffset field_offset,
361                                             const FieldValue& field_value) {
362   // TODO We may want to abort a transaction while still being in transaction mode. In this case,
363   // we'd need to disable the check.
364   constexpr bool kCheckTransaction = true;
365   switch (field_value.kind) {
366     case kBoolean:
367       if (UNLIKELY(field_value.is_volatile)) {
368         obj->SetFieldBooleanVolatile<false, kCheckTransaction>(field_offset,
369                                                          static_cast<bool>(field_value.value));
370       } else {
371         obj->SetFieldBoolean<false, kCheckTransaction>(field_offset,
372                                                  static_cast<bool>(field_value.value));
373       }
374       break;
375     case kByte:
376       if (UNLIKELY(field_value.is_volatile)) {
377         obj->SetFieldByteVolatile<false, kCheckTransaction>(field_offset,
378                                                          static_cast<int8_t>(field_value.value));
379       } else {
380         obj->SetFieldByte<false, kCheckTransaction>(field_offset,
381                                                  static_cast<int8_t>(field_value.value));
382       }
383       break;
384     case kChar:
385       if (UNLIKELY(field_value.is_volatile)) {
386         obj->SetFieldCharVolatile<false, kCheckTransaction>(field_offset,
387                                                           static_cast<uint16_t>(field_value.value));
388       } else {
389         obj->SetFieldChar<false, kCheckTransaction>(field_offset,
390                                                   static_cast<uint16_t>(field_value.value));
391       }
392       break;
393     case kShort:
394       if (UNLIKELY(field_value.is_volatile)) {
395         obj->SetFieldShortVolatile<false, kCheckTransaction>(field_offset,
396                                                           static_cast<int16_t>(field_value.value));
397       } else {
398         obj->SetFieldShort<false, kCheckTransaction>(field_offset,
399                                                   static_cast<int16_t>(field_value.value));
400       }
401       break;
402     case k32Bits:
403       if (UNLIKELY(field_value.is_volatile)) {
404         obj->SetField32Volatile<false, kCheckTransaction>(field_offset,
405                                                           static_cast<uint32_t>(field_value.value));
406       } else {
407         obj->SetField32<false, kCheckTransaction>(field_offset,
408                                                   static_cast<uint32_t>(field_value.value));
409       }
410       break;
411     case k64Bits:
412       if (UNLIKELY(field_value.is_volatile)) {
413         obj->SetField64Volatile<false, kCheckTransaction>(field_offset, field_value.value);
414       } else {
415         obj->SetField64<false, kCheckTransaction>(field_offset, field_value.value);
416       }
417       break;
418     case kReference:
419       if (UNLIKELY(field_value.is_volatile)) {
420         obj->SetFieldObjectVolatile<false, kCheckTransaction>(field_offset,
421                                                               reinterpret_cast<mirror::Object*>(field_value.value));
422       } else {
423         obj->SetFieldObject<false, kCheckTransaction>(field_offset,
424                                                       reinterpret_cast<mirror::Object*>(field_value.value));
425       }
426       break;
427     default:
428       LOG(FATAL) << "Unknown value kind " << static_cast<int>(field_value.kind);
429       break;
430   }
431 }
432 
VisitRoots(RootVisitor * visitor)433 void Transaction::ObjectLog::VisitRoots(RootVisitor* visitor) {
434   for (auto it : field_values_) {
435     FieldValue& field_value = it.second;
436     if (field_value.kind == ObjectLog::kReference) {
437       visitor->VisitRootIfNonNull(reinterpret_cast<mirror::Object**>(&field_value.value),
438                                   RootInfo(kRootUnknown));
439     }
440   }
441 }
442 
Undo(InternTable * intern_table)443 void Transaction::InternStringLog::Undo(InternTable* intern_table) {
444   DCHECK(intern_table != nullptr);
445   switch (string_op_) {
446     case InternStringLog::kInsert: {
447       switch (string_kind_) {
448         case InternStringLog::kStrongString:
449           intern_table->RemoveStrongFromTransaction(str_);
450           break;
451         case InternStringLog::kWeakString:
452           intern_table->RemoveWeakFromTransaction(str_);
453           break;
454         default:
455           LOG(FATAL) << "Unknown interned string kind";
456           break;
457       }
458       break;
459     }
460     case InternStringLog::kRemove: {
461       switch (string_kind_) {
462         case InternStringLog::kStrongString:
463           intern_table->InsertStrongFromTransaction(str_);
464           break;
465         case InternStringLog::kWeakString:
466           intern_table->InsertWeakFromTransaction(str_);
467           break;
468         default:
469           LOG(FATAL) << "Unknown interned string kind";
470           break;
471       }
472       break;
473     }
474     default:
475       LOG(FATAL) << "Unknown interned string op";
476       break;
477   }
478 }
479 
VisitRoots(RootVisitor * visitor)480 void Transaction::InternStringLog::VisitRoots(RootVisitor* visitor) {
481   visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&str_), RootInfo(kRootInternedString));
482 }
483 
LogValue(size_t index,uint64_t value)484 void Transaction::ArrayLog::LogValue(size_t index, uint64_t value) {
485   auto it = array_values_.find(index);
486   if (it == array_values_.end()) {
487     array_values_.insert(std::make_pair(index, value));
488   }
489 }
490 
Undo(mirror::Array * array)491 void Transaction::ArrayLog::Undo(mirror::Array* array) {
492   DCHECK(array != nullptr);
493   DCHECK(array->IsArrayInstance());
494   Primitive::Type type = array->GetClass()->GetComponentType()->GetPrimitiveType();
495   for (auto it : array_values_) {
496     UndoArrayWrite(array, type, it.first, it.second);
497   }
498 }
499 
UndoArrayWrite(mirror::Array * array,Primitive::Type array_type,size_t index,uint64_t value)500 void Transaction::ArrayLog::UndoArrayWrite(mirror::Array* array, Primitive::Type array_type,
501                                            size_t index, uint64_t value) {
502   // TODO We may want to abort a transaction while still being in transaction mode. In this case,
503   // we'd need to disable the check.
504   switch (array_type) {
505     case Primitive::kPrimBoolean:
506       array->AsBooleanArray()->SetWithoutChecks<false>(index, static_cast<uint8_t>(value));
507       break;
508     case Primitive::kPrimByte:
509       array->AsByteArray()->SetWithoutChecks<false>(index, static_cast<int8_t>(value));
510       break;
511     case Primitive::kPrimChar:
512       array->AsCharArray()->SetWithoutChecks<false>(index, static_cast<uint16_t>(value));
513       break;
514     case Primitive::kPrimShort:
515       array->AsShortArray()->SetWithoutChecks<false>(index, static_cast<int16_t>(value));
516       break;
517     case Primitive::kPrimInt:
518       array->AsIntArray()->SetWithoutChecks<false>(index, static_cast<int32_t>(value));
519       break;
520     case Primitive::kPrimFloat:
521       array->AsFloatArray()->SetWithoutChecks<false>(index, static_cast<float>(value));
522       break;
523     case Primitive::kPrimLong:
524       array->AsLongArray()->SetWithoutChecks<false>(index, static_cast<int64_t>(value));
525       break;
526     case Primitive::kPrimDouble:
527       array->AsDoubleArray()->SetWithoutChecks<false>(index, static_cast<double>(value));
528       break;
529     case Primitive::kPrimNot:
530       LOG(FATAL) << "ObjectArray should be treated as Object";
531       break;
532     default:
533       LOG(FATAL) << "Unsupported type " << array_type;
534   }
535 }
536 
537 }  // namespace art
538