Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 #include "vm/deopt_instructions.h" | 5 #include "vm/deopt_instructions.h" |
| 6 | 6 |
| 7 #include "vm/assembler.h" | 7 #include "vm/assembler.h" |
| 8 #include "vm/code_patcher.h" | 8 #include "vm/code_patcher.h" |
| 9 #include "vm/intermediate_language.h" | 9 #include "vm/intermediate_language.h" |
| 10 #include "vm/locations.h" | 10 #include "vm/locations.h" |
| 11 #include "vm/parser.h" | 11 #include "vm/parser.h" |
| 12 #include "vm/stack_frame.h" | 12 #include "vm/stack_frame.h" |
| 13 | 13 |
| 14 namespace dart { | 14 namespace dart { |
| 15 | 15 |
| 16 DEFINE_FLAG(bool, compress_deopt_info, true, | 16 DEFINE_FLAG(bool, compress_deopt_info, true, |
| 17 "Compress the size of the deoptimization info for optimized code."); | 17 "Compress the size of the deoptimization info for optimized code."); |
| 18 DECLARE_FLAG(bool, trace_deoptimization); | 18 DECLARE_FLAG(bool, trace_deoptimization); |
| 19 DECLARE_FLAG(bool, trace_deoptimization_verbose); | 19 DECLARE_FLAG(bool, trace_deoptimization_verbose); |
| 20 | 20 |
| 21 DeoptContext::DeoptContext(const Array& object_table, | 21 |
| 22 intptr_t num_args, | 22 DeoptContext::DeoptContext(const StackFrame* frame, |
| 23 DeoptReasonId deopt_reason) | 23 const Code& code, |
| 24 : object_table_(object_table.raw()), | 24 DestFrameOptions dest_options, |
| 25 fpu_register_t* fpu_registers, | |
| 26 intptr_t* cpu_registers) | |
| 27 : code_(code.raw()), | |
| 28 object_table_(Array::null()), | |
| 29 deopt_info_(DeoptInfo::null()), | |
| 30 dest_frame_is_allocated_(false), | |
| 25 dest_frame_(NULL), | 31 dest_frame_(NULL), |
| 26 dest_frame_size_(0), | 32 dest_frame_size_(0), |
| 27 source_frame_is_copy_(false), | 33 source_frame_is_allocated_(false), |
| 28 source_frame_(NULL), | 34 source_frame_(NULL), |
| 29 source_frame_size_(0), | 35 source_frame_size_(0), |
| 30 cpu_registers_(NULL), | 36 cpu_registers_(cpu_registers), |
| 31 fpu_registers_(NULL), | 37 fpu_registers_(fpu_registers), |
| 32 num_args_(num_args), | 38 num_args_(0), |
| 33 deopt_reason_(deopt_reason), | 39 deopt_reason_(kDeoptUnknown), |
| 34 isolate_(Isolate::Current()), | 40 isolate_(Isolate::Current()), |
| 35 deferred_boxes_(NULL), | 41 deferred_boxes_(NULL), |
| 36 deferred_object_refs_(NULL), | 42 deferred_object_refs_(NULL), |
| 37 deferred_objects_count_(0), | 43 deferred_objects_count_(0), |
| 38 deferred_objects_(NULL) { | 44 deferred_objects_(NULL) { |
| 45 object_table_ = code.object_table(); | |
| 46 | |
| 47 intptr_t deopt_reason = kDeoptUnknown; | |
| 48 const DeoptInfo& deopt_info = | |
| 49 DeoptInfo::Handle(code.GetDeoptInfoAtPc(frame->pc(), &deopt_reason)); | |
| 50 ASSERT(!deopt_info.IsNull()); | |
| 51 deopt_info_ = deopt_info.raw(); | |
| 52 deopt_reason_ = static_cast<DeoptReasonId>(deopt_reason); | |
| 53 | |
| 54 const Function& function = Function::Handle(code.function()); | |
| 55 num_args_ = | |
|
srdjan
2013/10/07 21:36:53
Please add comment why it is 0 with optional param
turnidge
2013/10/08 17:29:12
Copied the old comment from code_generator.cc.
| |
| 56 function.HasOptionalParameters() ? 0 : function.num_fixed_parameters(); | |
| 57 | |
| 58 // The fixed size section of the (fake) Dart frame called via a stub by the | |
| 59 // optimized function contains FP, PP (ARM and MIPS only), PC-marker and | |
| 60 // return-address. This section is copied as well, so that its contained | |
| 61 // values can be updated before returning to the deoptimized function. | |
| 62 source_frame_size_ = | |
| 63 + kDartFrameFixedSize // For saved values below sp. | |
| 64 + ((frame->fp() - frame->sp()) / kWordSize) // For frame size incl. sp. | |
| 65 + 1 // For fp. | |
| 66 + kParamEndSlotFromFp // For saved values above fp. | |
| 67 + num_args_; // For arguments. | |
| 68 source_frame_ = reinterpret_cast<intptr_t*>( | |
| 69 frame->sp() - (kDartFrameFixedSize * kWordSize)); | |
| 70 | |
| 71 if (dest_options == kDestIsOriginalFrame) { | |
| 72 // Work from a copy of the source frame. | |
| 73 intptr_t* original_frame = source_frame_; | |
| 74 source_frame_ = new intptr_t[source_frame_size_]; | |
| 75 ASSERT(source_frame_ != NULL); | |
| 76 for (intptr_t i = 0; i < source_frame_size_; i++) { | |
| 77 source_frame_[i] = original_frame[i]; | |
| 78 } | |
| 79 source_frame_is_allocated_ = true; | |
| 80 } | |
| 81 caller_fp_ = GetSourceFp(); | |
| 82 | |
| 83 dest_frame_size_ = deopt_info.FrameSize(); | |
| 84 | |
| 85 if (dest_options == kDestIsAllocated) { | |
| 86 dest_frame_ = new intptr_t[dest_frame_size_]; | |
| 87 ASSERT(source_frame_ != NULL); | |
| 88 for (intptr_t i = 0; i < source_frame_size_; i++) { | |
| 89 dest_frame_[i] = 0; | |
| 90 } | |
| 91 dest_frame_is_allocated_ = true; | |
| 92 } | |
| 93 | |
| 94 if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| 95 OS::PrintErr( | |
| 96 "Deoptimizing (reason %" Pd " '%s') at pc %#" Px " '%s' (count %d)\n", | |
| 97 deopt_reason, | |
| 98 DeoptReasonToText(deopt_reason_), | |
| 99 frame->pc(), | |
| 100 function.ToFullyQualifiedCString(), | |
| 101 function.deoptimization_counter()); | |
| 102 } | |
| 39 } | 103 } |
| 40 | 104 |
| 41 | 105 |
| 42 DeoptContext::~DeoptContext() { | 106 DeoptContext::~DeoptContext() { |
| 43 // Delete memory for source frame and registers. | 107 // Delete memory for source frame and registers. |
| 44 if (source_frame_is_copy_) { | 108 if (source_frame_is_allocated_) { |
| 45 delete[] source_frame_; | 109 delete[] source_frame_; |
| 46 } | 110 } |
| 47 source_frame_ = NULL; | 111 source_frame_ = NULL; |
| 48 delete[] fpu_registers_; | 112 delete[] fpu_registers_; |
| 49 delete[] cpu_registers_; | 113 delete[] cpu_registers_; |
| 50 fpu_registers_ = NULL; | 114 fpu_registers_ = NULL; |
| 51 cpu_registers_ = NULL; | 115 cpu_registers_ = NULL; |
| 116 if (dest_frame_is_allocated_) { | |
| 117 delete[] dest_frame_; | |
| 118 } | |
| 119 dest_frame_ = NULL; | |
| 52 | 120 |
| 53 // Delete all deferred objects. | 121 // Delete all deferred objects. |
| 54 for (intptr_t i = 0; i < deferred_objects_count_; i++) { | 122 for (intptr_t i = 0; i < deferred_objects_count_; i++) { |
| 55 delete deferred_objects_[i]; | 123 delete deferred_objects_[i]; |
| 56 } | 124 } |
| 57 delete[] deferred_objects_; | 125 delete[] deferred_objects_; |
| 58 deferred_objects_ = NULL; | 126 deferred_objects_ = NULL; |
| 59 deferred_objects_count_ = 0; | 127 deferred_objects_count_ = 0; |
| 60 } | 128 } |
| 61 | 129 |
| 62 | 130 |
| 63 void DeoptContext::SetSourceArgs(intptr_t* frame_start, | |
| 64 intptr_t frame_size, | |
| 65 fpu_register_t* fpu_registers, | |
| 66 intptr_t* cpu_registers, | |
| 67 bool source_frame_is_copy) { | |
| 68 ASSERT(frame_start != NULL); | |
| 69 ASSERT(frame_size >= 0); | |
| 70 ASSERT(source_frame_ == NULL); | |
| 71 ASSERT(cpu_registers_ == NULL && fpu_registers_ == NULL); | |
| 72 source_frame_ = frame_start; | |
| 73 source_frame_size_ = frame_size; | |
| 74 caller_fp_ = GetSourceFp(); | |
| 75 cpu_registers_ = cpu_registers; | |
| 76 fpu_registers_ = fpu_registers; | |
| 77 source_frame_is_copy_ = source_frame_is_copy; | |
| 78 } | |
| 79 | |
| 80 | |
| 81 void DeoptContext::SetDestArgs(intptr_t* frame_start, | |
| 82 intptr_t frame_size) { | |
| 83 ASSERT(frame_start != NULL); | |
| 84 ASSERT(frame_size >= 0); | |
| 85 ASSERT(dest_frame_ == NULL); | |
| 86 dest_frame_ = frame_start; | |
| 87 dest_frame_size_ = frame_size; | |
| 88 } | |
| 89 | |
| 90 | |
| 91 void DeoptContext::VisitObjectPointers(ObjectPointerVisitor* visitor) { | 131 void DeoptContext::VisitObjectPointers(ObjectPointerVisitor* visitor) { |
| 92 visitor->VisitPointer(reinterpret_cast<RawObject**>(&object_table_)); | 132 visitor->VisitPointer(reinterpret_cast<RawObject**>(&object_table_)); |
| 133 visitor->VisitPointer(reinterpret_cast<RawObject**>(&deopt_info_)); | |
| 134 | |
| 135 // Visit any object pointers on the destination stack. | |
| 136 if (dest_frame_is_allocated_) { | |
| 137 for (int i = 0; i < dest_frame_size_; i++) { | |
| 138 if (dest_frame_[i] != 0) { | |
| 139 visitor->VisitPointer(reinterpret_cast<RawObject**>(&dest_frame_[i])); | |
| 140 } | |
| 141 } | |
| 142 } | |
| 93 } | 143 } |
| 94 | 144 |
| 95 | 145 |
| 146 intptr_t DeoptContext::DestStackAdjustment() const { | |
| 147 return (dest_frame_size_ | |
| 148 - kDartFrameFixedSize | |
| 149 - num_args_ | |
| 150 - kParamEndSlotFromFp | |
| 151 - 1); // For fp. | |
| 152 } | |
| 153 | |
| 154 | |
| 96 intptr_t DeoptContext::GetSourceFp() const { | 155 intptr_t DeoptContext::GetSourceFp() const { |
| 97 return source_frame_[source_frame_size_ - 1 - num_args_ - | 156 return source_frame_[source_frame_size_ - 1 - num_args_ - |
| 98 kParamEndSlotFromFp]; | 157 kParamEndSlotFromFp]; |
| 99 } | 158 } |
| 100 | 159 |
| 101 | 160 |
| 102 intptr_t DeoptContext::GetSourcePp() const { | 161 intptr_t DeoptContext::GetSourcePp() const { |
| 103 return source_frame_[source_frame_size_ - 1 - num_args_ - | 162 return source_frame_[source_frame_size_ - 1 - num_args_ - |
| 104 kParamEndSlotFromFp + | 163 kParamEndSlotFromFp + |
| 105 StackFrame::SavedCallerPpSlotFromFp()]; | 164 StackFrame::SavedCallerPpSlotFromFp()]; |
| 106 } | 165 } |
| 107 | 166 |
| 108 | 167 |
| 109 intptr_t DeoptContext::GetSourcePc() const { | 168 intptr_t DeoptContext::GetSourcePc() const { |
| 110 return source_frame_[source_frame_size_ - num_args_ + kSavedPcSlotFromSp]; | 169 return source_frame_[source_frame_size_ - num_args_ + kSavedPcSlotFromSp]; |
| 111 } | 170 } |
| 112 | 171 |
| 113 | 172 |
| 114 intptr_t DeoptContext::GetCallerFp() const { | 173 intptr_t DeoptContext::GetCallerFp() const { |
| 115 return caller_fp_; | 174 return caller_fp_; |
| 116 } | 175 } |
| 117 | 176 |
| 118 | 177 |
| 119 void DeoptContext::SetCallerFp(intptr_t caller_fp) { | 178 void DeoptContext::SetCallerFp(intptr_t caller_fp) { |
| 120 caller_fp_ = caller_fp; | 179 caller_fp_ = caller_fp; |
| 121 } | 180 } |
| 122 | 181 |
| 123 | 182 |
| 183 static bool IsObjectInstruction(DeoptInstr::Kind kind) { | |
| 184 switch (kind) { | |
| 185 case DeoptInstr::kConstant: | |
| 186 case DeoptInstr::kRegister: | |
| 187 case DeoptInstr::kFpuRegister: | |
| 188 case DeoptInstr::kInt64FpuRegister: | |
| 189 case DeoptInstr::kFloat32x4FpuRegister: | |
| 190 case DeoptInstr::kUint32x4FpuRegister: | |
| 191 case DeoptInstr::kStackSlot: | |
| 192 case DeoptInstr::kDoubleStackSlot: | |
| 193 case DeoptInstr::kInt64StackSlot: | |
| 194 case DeoptInstr::kFloat32x4StackSlot: | |
| 195 case DeoptInstr::kUint32x4StackSlot: | |
| 196 case DeoptInstr::kPp: | |
| 197 case DeoptInstr::kCallerPp: | |
| 198 case DeoptInstr::kMaterializedObjectRef: | |
| 199 return true; | |
| 200 | |
| 201 case DeoptInstr::kRetAddress: | |
| 202 case DeoptInstr::kPcMarker: | |
| 203 case DeoptInstr::kCallerFp: | |
| 204 case DeoptInstr::kCallerPc: | |
| 205 return false; | |
| 206 | |
| 207 case DeoptInstr::kSuffix: | |
| 208 case DeoptInstr::kMaterializeObject: | |
| 209 // We should not encounter these instructions when filling stack slots. | |
| 210 UNIMPLEMENTED(); | |
| 211 return false; | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 | |
| 216 void DeoptContext::FillDestFrame() { | |
| 217 const Code& code = Code::Handle(code_); | |
| 218 const DeoptInfo& deopt_info = DeoptInfo::Handle(deopt_info_); | |
| 219 | |
| 220 const intptr_t len = deopt_info.TranslationLength(); | |
| 221 GrowableArray<DeoptInstr*> deopt_instructions(len); | |
| 222 const Array& deopt_table = Array::Handle(code.deopt_info_array()); | |
| 223 ASSERT(!deopt_table.IsNull()); | |
| 224 deopt_info.ToInstructions(deopt_table, &deopt_instructions); | |
| 225 | |
| 226 const intptr_t frame_size = deopt_info.FrameSize(); | |
| 227 | |
| 228 // For now, we never place non-objects in the deoptimized frame if | |
| 229 // the destination frame is a copy. This allows us to copy the | |
| 230 // deoptimized frame into an Array. | |
| 231 const bool objects_only = dest_frame_is_allocated_; | |
| 232 | |
| 233 // All kMaterializeObject instructions are emitted before the instructions | |
| 234 // that describe stack frames. Skip them and defer materialization of | |
| 235 // objects until the frame is fully reconstructed and it is safe to perform | |
| 236 // GC. | |
| 237 // Arguments (class of the instance to allocate and field-value pairs) are | |
| 238 // described as part of the expression stack for the bottom-most deoptimized | |
| 239 // frame. They will be used during materialization and removed from the stack | |
| 240 // right before control switches to the unoptimized code. | |
| 241 const intptr_t num_materializations = len - frame_size; | |
| 242 PrepareForDeferredMaterialization(num_materializations); | |
| 243 for (intptr_t from_index = 0, to_index = kDartFrameFixedSize; | |
| 244 from_index < num_materializations; | |
| 245 from_index++) { | |
| 246 const intptr_t field_count = | |
| 247 DeoptInstr::GetFieldCount(deopt_instructions[from_index]); | |
| 248 intptr_t* args = GetDestFrameAddressAt(to_index); | |
| 249 DeferredObject* obj = new DeferredObject(field_count, args); | |
| 250 SetDeferredObjectAt(from_index, obj); | |
| 251 to_index += obj->ArgumentCount(); | |
| 252 } | |
| 253 | |
| 254 // Populate stack frames. | |
| 255 for (intptr_t to_index = frame_size - 1, from_index = len - 1; | |
| 256 to_index >= 0; | |
| 257 to_index--, from_index--) { | |
| 258 intptr_t* to_addr = GetDestFrameAddressAt(to_index); | |
| 259 DeoptInstr* instr = deopt_instructions[from_index]; | |
| 260 if (!objects_only || IsObjectInstruction(instr->kind())) { | |
| 261 instr->Execute(this, to_addr); | |
| 262 } else { | |
| 263 *to_addr = 0; | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 if (FLAG_trace_deoptimization_verbose) { | |
| 268 intptr_t* start = dest_frame_; | |
| 269 for (intptr_t i = 0; i < frame_size; i++) { | |
| 270 // OS::PrintErr("*%" Pd ". [%" Px "] %#014" Px " [%s]\n", | |
| 271 OS::Print("*%" Pd ". [%" Px "] %#014" Px " [%s]\n", | |
| 272 i, | |
| 273 reinterpret_cast<uword>(&start[i]), | |
| 274 start[i], | |
| 275 deopt_instructions[i + (len - frame_size)]->ToCString()); | |
| 276 } | |
| 277 } | |
| 278 } | |
| 279 | |
| 280 | |
| 124 static void FillDeferredSlots(DeferredSlot** slot_list) { | 281 static void FillDeferredSlots(DeferredSlot** slot_list) { |
| 125 DeferredSlot* slot = *slot_list; | 282 DeferredSlot* slot = *slot_list; |
| 126 *slot_list = NULL; | 283 *slot_list = NULL; |
| 127 | 284 |
| 128 while (slot != NULL) { | 285 while (slot != NULL) { |
| 129 DeferredSlot* current = slot; | 286 DeferredSlot* current = slot; |
| 130 slot = slot->next(); | 287 slot = slot->next(); |
| 131 | 288 |
| 132 current->Materialize(); | 289 current->Materialize(); |
| 133 | 290 |
| 134 delete current; | 291 delete current; |
| 135 } | 292 } |
| 136 } | 293 } |
| 137 | 294 |
| 138 | 295 |
| 139 // Materializes all deferred objects. Returns the total number of | 296 // Materializes all deferred objects. Returns the total number of |
| 140 // artificial arguments used during deoptimization. | 297 // artificial arguments used during deoptimization. |
| 141 intptr_t DeoptContext::MaterializeDeferredObjects() { | 298 intptr_t DeoptContext::MaterializeDeferredObjects() { |
| 142 // First materialize all unboxed "primitive" values (doubles, mints, simd) | 299 // First materialize all unboxed "primitive" values (doubles, mints, simd) |
| 143 // then materialize objects. The order is important: objects might be | 300 // then materialize objects. The order is important: objects might be |
| 144 // referencing boxes allocated on the first step. At the same time | 301 // referencing boxes allocated on the first step. At the same time |
| 145 // objects can't be referencing other deferred objects because storing | 302 // objects can't be referencing other deferred objects because storing |
| 146 // an object into a field is always conservatively treated as escaping by | 303 // an object into a field is always conservatively treated as escaping by |
| 147 // allocation sinking and load forwarding. | 304 // allocation sinking and load forwarding. |
| 148 FillDeferredSlots(&deferred_boxes_); | 305 FillDeferredSlots(&deferred_boxes_); |
| 149 FillDeferredSlots(&deferred_object_refs_); | 306 FillDeferredSlots(&deferred_object_refs_); |
| 150 | 307 |
| 151 // Compute total number of artificial arguments used during deoptimization. | 308 // Compute total number of artificial arguments used during deoptimization. |
| 152 intptr_t deopt_arguments = 0; | 309 intptr_t deopt_arg_count = 0; |
| 153 for (intptr_t i = 0; i < DeferredObjectsCount(); i++) { | 310 for (intptr_t i = 0; i < DeferredObjectsCount(); i++) { |
| 154 deopt_arguments += GetDeferredObject(i)->ArgumentCount(); | 311 deopt_arg_count += GetDeferredObject(i)->ArgumentCount(); |
| 155 } | 312 } |
| 156 return deopt_arguments; | 313 |
| 314 // Since this is the only step where GC can occur during deoptimization, | |
| 315 // use it to report the source line where deoptimization occured. | |
| 316 if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| 317 DartFrameIterator iterator; | |
| 318 StackFrame* top_frame = iterator.NextFrame(); | |
| 319 ASSERT(top_frame != NULL); | |
| 320 const Code& code = Code::Handle(top_frame->LookupDartCode()); | |
| 321 const Function& top_function = Function::Handle(code.function()); | |
| 322 const Script& script = Script::Handle(top_function.script()); | |
| 323 const intptr_t token_pos = code.GetTokenIndexOfPC(top_frame->pc()); | |
| 324 intptr_t line, column; | |
| 325 script.GetTokenLocation(token_pos, &line, &column); | |
| 326 String& line_string = String::Handle(script.GetLine(line)); | |
| 327 OS::PrintErr(" Function: %s\n", top_function.ToFullyQualifiedCString()); | |
| 328 OS::PrintErr(" Line %" Pd ": '%s'\n", line, line_string.ToCString()); | |
| 329 OS::PrintErr(" Deopt args: %" Pd "\n", deopt_arg_count); | |
| 330 } | |
| 331 | |
| 332 return deopt_arg_count; | |
| 333 } | |
| 334 | |
| 335 | |
| 336 RawArray* DeoptContext::DestFrameAsArray() { | |
| 337 ASSERT(dest_frame_ != NULL && dest_frame_is_allocated_); | |
| 338 const Array& dest_array = | |
| 339 Array::Handle(Array::New(dest_frame_size_)); | |
| 340 Instance& obj = Instance::Handle(); | |
| 341 for (intptr_t i = 0; i < dest_frame_size_; i++) { | |
| 342 obj ^= reinterpret_cast<RawObject*>(dest_frame_[i]); | |
| 343 dest_array.SetAt(i, obj); | |
| 344 } | |
| 345 return dest_array.raw(); | |
| 157 } | 346 } |
| 158 | 347 |
| 159 | 348 |
| 160 // Deoptimization instruction moving value from optimized frame at | 349 // Deoptimization instruction moving value from optimized frame at |
| 161 // 'source_index' to specified slots in the unoptimized frame. | 350 // 'source_index' to specified slots in the unoptimized frame. |
| 162 // 'source_index' represents the slot index of the frame (0 being | 351 // 'source_index' represents the slot index of the frame (0 being |
| 163 // first argument) and accounts for saved return address, frame | 352 // first argument) and accounts for saved return address, frame |
| 164 // pointer, pool pointer and pc marker. | 353 // pointer, pool pointer and pc marker. |
| 165 class DeoptStackSlotInstr : public DeoptInstr { | 354 class DeoptStackSlotInstr : public DeoptInstr { |
| 166 public: | 355 public: |
| (...skipping 1027 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1194 Smi* offset, | 1383 Smi* offset, |
| 1195 DeoptInfo* info, | 1384 DeoptInfo* info, |
| 1196 Smi* reason) { | 1385 Smi* reason) { |
| 1197 intptr_t i = index * kEntrySize; | 1386 intptr_t i = index * kEntrySize; |
| 1198 *offset ^= table.At(i); | 1387 *offset ^= table.At(i); |
| 1199 *info ^= table.At(i + 1); | 1388 *info ^= table.At(i + 1); |
| 1200 *reason ^= table.At(i + 2); | 1389 *reason ^= table.At(i + 2); |
| 1201 } | 1390 } |
| 1202 | 1391 |
| 1203 } // namespace dart | 1392 } // namespace dart |
| OLD | NEW |