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/globals.h" | 5 #include "vm/globals.h" |
| 6 #if defined(TARGET_ARCH_ARM) | 6 #if defined(TARGET_ARCH_ARM) |
| 7 | 7 |
| 8 #include "vm/assembler.h" | 8 #include "vm/assembler.h" |
| 9 #include "vm/code_generator.h" | 9 #include "vm/code_generator.h" |
| 10 #include "vm/dart_entry.h" | 10 #include "vm/dart_entry.h" |
| 11 #include "vm/flow_graph_compiler.h" | |
| 11 #include "vm/instructions.h" | 12 #include "vm/instructions.h" |
| 12 #include "vm/stack_frame.h" | 13 #include "vm/stack_frame.h" |
| 13 #include "vm/stub_code.h" | 14 #include "vm/stub_code.h" |
| 14 | 15 |
| 15 #define __ assembler-> | 16 #define __ assembler-> |
| 16 | 17 |
| 17 namespace dart { | 18 namespace dart { |
| 18 | 19 |
| 20 DEFINE_FLAG(bool, inline_alloc, true, "Inline allocation of objects."); | |
| 21 DEFINE_FLAG(bool, use_slow_path, false, | |
| 22 "Set to true for debugging & verifying the slow paths."); | |
| 23 DECLARE_FLAG(int, optimization_counter_threshold); | |
| 24 DECLARE_FLAG(bool, trace_optimized_ic_calls); | |
| 25 | |
| 26 | |
| 19 // Input parameters: | 27 // Input parameters: |
| 20 // LR : return address. | 28 // LR : return address. |
| 21 // SP : address of last argument in argument array. | 29 // SP : address of last argument in argument array. |
| 22 // SP + 4*R4 - 4 : address of first argument in argument array. | 30 // SP + 4*R4 - 4 : address of first argument in argument array. |
| 23 // SP + 4*R4 : address of return value. | 31 // SP + 4*R4 : address of return value. |
| 24 // R5 : address of the runtime function to call. | 32 // R5 : address of the runtime function to call. |
| 25 // R4 : number of arguments to the call. | 33 // R4 : number of arguments to the call. |
| 26 void StubCode::GenerateCallToRuntimeStub(Assembler* assembler) { | 34 void StubCode::GenerateCallToRuntimeStub(Assembler* assembler) { |
| 27 const intptr_t isolate_offset = NativeArguments::isolate_offset(); | 35 const intptr_t isolate_offset = NativeArguments::isolate_offset(); |
| 28 const intptr_t argc_tag_offset = NativeArguments::argc_tag_offset(); | 36 const intptr_t argc_tag_offset = NativeArguments::argc_tag_offset(); |
| (...skipping 317 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 346 void StubCode::GenerateAllocateContextStub(Assembler* assembler) { | 354 void StubCode::GenerateAllocateContextStub(Assembler* assembler) { |
| 347 __ Unimplemented("AllocateContext stub"); | 355 __ Unimplemented("AllocateContext stub"); |
| 348 } | 356 } |
| 349 | 357 |
| 350 | 358 |
| 351 void StubCode::GenerateUpdateStoreBufferStub(Assembler* assembler) { | 359 void StubCode::GenerateUpdateStoreBufferStub(Assembler* assembler) { |
| 352 __ Unimplemented("UpdateStoreBuffer stub"); | 360 __ Unimplemented("UpdateStoreBuffer stub"); |
| 353 } | 361 } |
| 354 | 362 |
| 355 | 363 |
| 364 // Called for inline allocation of objects. | |
| 365 // Input parameters: | |
| 366 // LR : return address. | |
| 367 // SP + 4 : type arguments object (only if class is parameterized). | |
| 368 // SP + 0 : type arguments of instantiator (only if class is parameterized). | |
| 356 void StubCode::GenerateAllocationStubForClass(Assembler* assembler, | 369 void StubCode::GenerateAllocationStubForClass(Assembler* assembler, |
| 357 const Class& cls) { | 370 const Class& cls) { |
| 358 __ Unimplemented("AllocateObject stub"); | 371 // The generated code is different if the class is parameterized. |
| 372 const bool is_cls_parameterized = | |
| 373 cls.type_arguments_field_offset() != Class::kNoTypeArguments; | |
| 374 // kInlineInstanceSize is a constant used as a threshold for determining | |
| 375 // when the object initialization should be done as a loop or as | |
| 376 // straight line code. | |
| 377 const int kInlineInstanceSize = 12; | |
|
zra
2013/04/03 17:39:47
Why 12? Should this be a flag?
regis
2013/04/03 20:06:07
The flag would only be useful until the value is t
| |
| 378 const intptr_t instance_size = cls.instance_size(); | |
| 379 ASSERT(instance_size > 0); | |
| 380 const intptr_t type_args_size = InstantiatedTypeArguments::InstanceSize(); | |
| 381 if (FLAG_inline_alloc && | |
| 382 PageSpace::IsPageAllocatableSize(instance_size + type_args_size)) { | |
| 383 Label slow_case; | |
| 384 Heap* heap = Isolate::Current()->heap(); | |
| 385 __ LoadImmediate(R5, heap->TopAddress()); | |
| 386 __ ldr(R2, Address(R5, 0)); | |
| 387 __ AddImmediate(R3, R2, instance_size); | |
| 388 if (is_cls_parameterized) { | |
| 389 __ ldm(IA, SP, (1 << R0) | (1 << R1)); | |
| 390 __ mov(R4, ShifterOperand(R3)); | |
| 391 // A new InstantiatedTypeArguments object only needs to be allocated if | |
| 392 // the instantiator is provided (not kNoInstantiator, but may be null). | |
| 393 __ CompareImmediate(R0, Smi::RawValue(StubCode::kNoInstantiator)); | |
| 394 __ AddImmediate(R3, type_args_size, NE); | |
| 395 // R4: potential new object end and, if R4 != R3, potential new | |
| 396 // InstantiatedTypeArguments object start. | |
| 397 } | |
| 398 // Check if the allocation fits into the remaining space. | |
| 399 // R2: potential new object start. | |
| 400 // R3: potential next object start. | |
| 401 if (FLAG_use_slow_path) { | |
| 402 __ b(&slow_case); | |
| 403 } else { | |
| 404 __ LoadImmediate(IP, heap->EndAddress()); | |
| 405 __ cmp(R3, ShifterOperand(IP)); | |
| 406 __ b(&slow_case, CS); // Branch if unsigned higher or equal. | |
| 407 } | |
| 408 | |
| 409 // Successfully allocated the object(s), now update top to point to | |
| 410 // next object start and initialize the object. | |
| 411 __ str(R3, Address(R5, 0)); | |
| 412 | |
| 413 if (is_cls_parameterized) { | |
| 414 // Initialize the type arguments field in the object. | |
| 415 // R2: new object start. | |
| 416 // R4: potential new object end and, if R4 != R3, potential new | |
| 417 // InstantiatedTypeArguments object start. | |
| 418 // R3: next object start. | |
| 419 Label type_arguments_ready; | |
| 420 __ cmp(R4, ShifterOperand(R3)); | |
| 421 __ b(&type_arguments_ready, EQ); | |
| 422 // Initialize InstantiatedTypeArguments object at R4. | |
| 423 __ str(R1, Address(R4, | |
| 424 InstantiatedTypeArguments::uninstantiated_type_arguments_offset())); | |
| 425 __ str(R0, Address(R4, | |
| 426 InstantiatedTypeArguments::instantiator_type_arguments_offset())); | |
| 427 const Class& ita_cls = | |
| 428 Class::ZoneHandle(Object::instantiated_type_arguments_class()); | |
| 429 // Set the tags. | |
| 430 uword tags = 0; | |
| 431 tags = RawObject::SizeTag::update(type_args_size, tags); | |
| 432 tags = RawObject::ClassIdTag::update(ita_cls.id(), tags); | |
| 433 __ LoadImmediate(R0, tags); | |
| 434 __ str(R0, Address(R4, Instance::tags_offset())); | |
| 435 // Set the new InstantiatedTypeArguments object (R4) as the type | |
| 436 // arguments (R1) of the new object (R2). | |
| 437 __ add(R1, R4, ShifterOperand(kHeapObjectTag)); | |
| 438 // Set R3 to new object end. | |
| 439 __ mov(R3, ShifterOperand(R4)); | |
| 440 __ Bind(&type_arguments_ready); | |
| 441 // R2: new object. | |
| 442 // R1: new object type arguments. | |
| 443 } | |
| 444 | |
| 445 // R2: new object start. | |
| 446 // R3: next object start. | |
| 447 // R1: new object type arguments (if is_cls_parameterized). | |
| 448 // Set the tags. | |
| 449 uword tags = 0; | |
| 450 tags = RawObject::SizeTag::update(instance_size, tags); | |
| 451 ASSERT(cls.id() != kIllegalCid); | |
| 452 tags = RawObject::ClassIdTag::update(cls.id(), tags); | |
| 453 __ LoadImmediate(R0, tags); | |
| 454 __ str(R0, Address(R2, Instance::tags_offset())); | |
| 455 | |
| 456 // Initialize the remaining words of the object. | |
| 457 __ LoadImmediate(R0, reinterpret_cast<intptr_t>(Object::null())); | |
| 458 | |
| 459 // R0: raw null. | |
| 460 // R2: new object start. | |
| 461 // R3: next object start. | |
| 462 // R1: new object type arguments (if is_cls_parameterized). | |
| 463 // First try inlining the initialization without a loop. | |
| 464 if (instance_size < (kInlineInstanceSize * kWordSize)) { | |
| 465 // Check if the object contains any non-header fields. | |
| 466 // Small objects are initialized using a consecutive set of writes. | |
| 467 for (intptr_t current_offset = sizeof(RawObject); | |
| 468 current_offset < instance_size; | |
| 469 current_offset += kWordSize) { | |
| 470 __ StoreToOffset(kStoreWord, R0, R2, current_offset); | |
| 471 } | |
| 472 } else { | |
| 473 __ add(R4, R2, ShifterOperand(sizeof(RawObject))); | |
| 474 // Loop until the whole object is initialized. | |
| 475 // R0: raw null. | |
| 476 // R2: new object. | |
| 477 // R3: next object start. | |
| 478 // R4: next word to be initialized. | |
| 479 // R1: new object type arguments (if is_cls_parameterized). | |
| 480 Label init_loop; | |
| 481 Label done; | |
| 482 __ Bind(&init_loop); | |
| 483 __ cmp(R4, ShifterOperand(R3)); | |
| 484 __ b(&done, CS); | |
| 485 __ str(R0, Address(R4, 0)); | |
| 486 __ AddImmediate(R4, kWordSize); | |
| 487 __ b(&init_loop); | |
| 488 __ Bind(&done); | |
| 489 } | |
| 490 if (is_cls_parameterized) { | |
| 491 // R1: new object type arguments. | |
| 492 // Set the type arguments in the new object. | |
| 493 __ StoreToOffset(kStoreWord, R1, R2, cls.type_arguments_field_offset()); | |
| 494 } | |
| 495 // Done allocating and initializing the instance. | |
| 496 // R2: new object still missing its heap tag. | |
| 497 __ add(R0, R2, ShifterOperand(kHeapObjectTag)); | |
| 498 __ Ret(); | |
| 499 | |
| 500 __ Bind(&slow_case); | |
| 501 } | |
| 502 if (is_cls_parameterized) { | |
| 503 __ ldm(IA, SP, (1 << R0) | (1 << R1)); | |
| 504 } | |
| 505 // Create a stub frame as we are pushing some objects on the stack before | |
| 506 // calling into the runtime. | |
| 507 __ EnterStubFrame(true); // Uses pool pointer to pass cls to runtime. | |
| 508 __ LoadImmediate(R2, reinterpret_cast<intptr_t>(Object::null())); | |
| 509 __ Push(R2); // Setup space on stack for return value. | |
| 510 __ PushObject(cls); // Push class of object to be allocated. | |
| 511 if (is_cls_parameterized) { | |
| 512 // Push type arguments of object to be allocated and of instantiator. | |
| 513 __ PushList((1 << R0) | (1 << R1)); | |
| 514 } else { | |
| 515 // Push null type arguments and kNoInstantiator. | |
| 516 __ LoadImmediate(R1, Smi::RawValue(StubCode::kNoInstantiator)); | |
| 517 __ PushList((1 << R1) | (1 << R2)); | |
| 518 } | |
| 519 __ CallRuntime(kAllocateObjectRuntimeEntry); // Allocate object. | |
| 520 __ Drop(3); // Pop arguments. | |
| 521 __ Pop(R0); // Pop result (newly allocated object). | |
| 522 // R0: new object | |
| 523 // Restore the frame pointer. | |
| 524 __ LeaveStubFrame(true); | |
| 525 __ Ret(); | |
| 359 } | 526 } |
| 360 | 527 |
| 361 | 528 |
| 362 void StubCode::GenerateAllocationStubForClosure(Assembler* assembler, | 529 void StubCode::GenerateAllocationStubForClosure(Assembler* assembler, |
| 363 const Function& func) { | 530 const Function& func) { |
| 364 __ Unimplemented("AllocateClosure stub"); | 531 __ Unimplemented("AllocateClosure stub"); |
| 365 } | 532 } |
| 366 | 533 |
| 367 | 534 |
| 368 void StubCode::GenerateCallNoSuchMethodFunctionStub(Assembler* assembler) { | 535 void StubCode::GenerateCallNoSuchMethodFunctionStub(Assembler* assembler) { |
| 369 __ Unimplemented("CallNoSuchMethodFunction stub"); | 536 __ Unimplemented("CallNoSuchMethodFunction stub"); |
| 370 } | 537 } |
| 371 | 538 |
| 372 | 539 |
| 373 void StubCode::GenerateOptimizedUsageCounterIncrement(Assembler* assembler) { | 540 void StubCode::GenerateOptimizedUsageCounterIncrement(Assembler* assembler) { |
| 374 __ Unimplemented("OptimizedUsageCounterIncrement stub"); | 541 __ Unimplemented("OptimizedUsageCounterIncrement stub"); |
| 375 } | 542 } |
| 376 | 543 |
| 377 | 544 |
| 545 // Loads function into 'temp_reg'. | |
| 378 void StubCode::GenerateUsageCounterIncrement(Assembler* assembler, | 546 void StubCode::GenerateUsageCounterIncrement(Assembler* assembler, |
| 379 Register temp_reg) { | 547 Register temp_reg) { |
| 380 __ Unimplemented("UsageCounterIncrement stub"); | 548 Register ic_reg = R5; |
| 381 } | 549 Register func_reg = temp_reg; |
| 382 | 550 ASSERT(temp_reg == R6); |
| 383 | 551 __ ldr(func_reg, FieldAddress(ic_reg, ICData::function_offset())); |
| 552 __ ldr(R7, FieldAddress(func_reg, Function::usage_counter_offset())); | |
| 553 Label is_hot; | |
| 554 if (FlowGraphCompiler::CanOptimize()) { | |
| 555 ASSERT(FLAG_optimization_counter_threshold > 1); | |
| 556 // The usage_counter is always less than FLAG_optimization_counter_threshold | |
| 557 // except when the function gets optimized. | |
| 558 __ CompareImmediate(R7, FLAG_optimization_counter_threshold); | |
| 559 __ b(&is_hot, EQ); | |
| 560 // As long as VM has no OSR do not optimize in the middle of the function | |
| 561 // but only at exit so that we have collected all type feedback before | |
| 562 // optimizing. | |
| 563 } | |
| 564 __ add(R7, R7, ShifterOperand(1)); | |
| 565 __ str(R7, FieldAddress(func_reg, Function::usage_counter_offset())); | |
| 566 __ Bind(&is_hot); | |
| 567 } | |
| 568 | |
| 569 | |
| 570 // Generate inline cache check for 'num_args'. | |
| 571 // LR: return address | |
| 572 // R5: Inline cache data object. | |
| 573 // R4: Arguments descriptor array. | |
| 574 // Control flow: | |
| 575 // - If receiver is null -> jump to IC miss. | |
| 576 // - If receiver is Smi -> load Smi class. | |
| 577 // - If receiver is not-Smi -> load receiver's class. | |
| 578 // - Check if 'num_args' (including receiver) match any IC data group. | |
| 579 // - Match found -> jump to target. | |
| 580 // - Match not found -> jump to IC miss. | |
| 384 void StubCode::GenerateNArgsCheckInlineCacheStub(Assembler* assembler, | 581 void StubCode::GenerateNArgsCheckInlineCacheStub(Assembler* assembler, |
| 385 intptr_t num_args) { | 582 intptr_t num_args) { |
| 386 __ Unimplemented("NArgsCheckInlineCache stub"); | 583 ASSERT(num_args > 0); |
| 387 } | 584 #if defined(DEBUG) |
| 388 | 585 { Label ok; |
| 389 | 586 // Check that the IC data array has NumberOfArgumentsChecked() == num_args. |
| 587 // 'num_args_tested' is stored as an untagged int. | |
| 588 __ ldr(R6, FieldAddress(R5, ICData::num_args_tested_offset())); | |
| 589 __ CompareImmediate(R6, num_args); | |
| 590 __ b(&ok, EQ); | |
| 591 __ Stop("Incorrect stub for IC data"); | |
| 592 __ Bind(&ok); | |
| 593 } | |
| 594 #endif // DEBUG | |
| 595 | |
| 596 // Preserve return address, since LR is needed for subroutine call. | |
| 597 __ mov(R8, ShifterOperand(LR)); | |
| 598 // Loop that checks if there is an IC data match. | |
| 599 Label loop, update, test, found, get_class_id_as_smi; | |
| 600 // R5: IC data object (preserved). | |
| 601 __ ldr(R6, FieldAddress(R5, ICData::ic_data_offset())); | |
| 602 // R6: ic_data_array with check entries: classes and target functions. | |
| 603 __ AddImmediate(R6, R6, Array::data_offset() - kHeapObjectTag); | |
| 604 // R6: points directly to the first ic data array element. | |
| 605 | |
| 606 // Get the receiver's class ID (first read number of arguments from | |
| 607 // arguments descriptor array and then access the receiver from the stack). | |
| 608 __ ldr(R7, FieldAddress(R4, ArgumentsDescriptor::count_offset())); | |
| 609 __ sub(R7, R7, ShifterOperand(Smi::RawValue(1))); | |
| 610 __ ldr(R0, Address(SP, R7, LSL, 1)); // R7 (argument_count - 1) is smi. | |
| 611 __ bl(&get_class_id_as_smi); | |
| 612 // R7: argument_count - 1 (smi). | |
| 613 // R0: receiver's class ID (smi). | |
| 614 __ ldr(R1, Address(R6, 0)); // First class id (smi) to check. | |
| 615 __ b(&test); | |
| 616 | |
| 617 __ Bind(&loop); | |
| 618 for (int i = 0; i < num_args; i++) { | |
| 619 if (i > 0) { | |
| 620 // If not the first, load the next argument's class ID. | |
| 621 __ AddImmediate(R0, R7, Smi::RawValue(-i)); | |
| 622 __ ldr(R0, Address(SP, R0, LSL, 1)); | |
| 623 __ bl(&get_class_id_as_smi); | |
| 624 // R0: next argument class ID (smi). | |
| 625 __ LoadFromOffset(kLoadWord, R1, R6, i * kWordSize); | |
| 626 // R1: next class ID to check (smi). | |
| 627 } | |
| 628 __ cmp(R0, ShifterOperand(R1)); // Class id match? | |
| 629 if (i < (num_args - 1)) { | |
| 630 __ b(&update, NE); // Continue. | |
| 631 } else { | |
| 632 // Last check, all checks before matched. | |
| 633 __ mov(LR, ShifterOperand(R8), EQ); // Restore return address if found. | |
| 634 __ b(&found, EQ); // Break. | |
| 635 } | |
| 636 } | |
| 637 __ Bind(&update); | |
| 638 // Reload receiver class ID. It has not been destroyed when num_args == 1. | |
| 639 if (num_args > 1) { | |
| 640 __ ldr(R0, Address(SP, R7, LSL, 1)); | |
| 641 __ bl(&get_class_id_as_smi); | |
| 642 } | |
| 643 | |
| 644 const intptr_t entry_size = ICData::TestEntryLengthFor(num_args) * kWordSize; | |
| 645 __ AddImmediate(R6, entry_size); // Next entry. | |
| 646 __ ldr(R1, Address(R6, 0)); // Next class ID. | |
| 647 | |
| 648 __ Bind(&test); | |
| 649 __ CompareImmediate(R1, Smi::RawValue(kIllegalCid)); // Done? | |
| 650 __ b(&loop, NE); | |
| 651 | |
| 652 // IC miss. | |
| 653 // Restore return address. | |
| 654 __ mov(LR, ShifterOperand(R8)); | |
| 655 | |
| 656 // Compute address of arguments (first read number of arguments from | |
| 657 // arguments descriptor array and then compute address on the stack). | |
| 658 // R7: argument_count - 1 (smi). | |
| 659 __ add(R7, SP, ShifterOperand(R7, LSL, 1)); // R7 is Smi. | |
| 660 // R7: address of receiver. | |
| 661 // Create a stub frame as we are pushing some objects on the stack before | |
| 662 // calling into the runtime. | |
| 663 __ EnterStubFrame(); | |
| 664 __ LoadImmediate(R0, reinterpret_cast<intptr_t>(Object::null())); | |
| 665 // Preserve IC data object and arguments descriptor array and | |
| 666 // setup space on stack for result (target code object). | |
| 667 __ PushList((1 << R0) | (1 << R4) | (1 << R5)); | |
| 668 // Push call arguments. | |
| 669 for (intptr_t i = 0; i < num_args; i++) { | |
| 670 __ LoadFromOffset(kLoadWord, IP, R7, -i * kWordSize); | |
| 671 __ Push(IP); | |
| 672 } | |
| 673 // Pass IC data object and arguments descriptor array. | |
| 674 __ PushList((1 << R4) | (1 << R5)); | |
| 675 | |
| 676 if (num_args == 1) { | |
| 677 __ CallRuntime(kInlineCacheMissHandlerOneArgRuntimeEntry); | |
| 678 } else if (num_args == 2) { | |
| 679 __ CallRuntime(kInlineCacheMissHandlerTwoArgsRuntimeEntry); | |
| 680 } else if (num_args == 3) { | |
| 681 __ CallRuntime(kInlineCacheMissHandlerThreeArgsRuntimeEntry); | |
| 682 } else { | |
| 683 UNIMPLEMENTED(); | |
| 684 } | |
| 685 // Remove the call arguments pushed earlier, including the IC data object | |
| 686 // and the arguments descriptor array. | |
| 687 __ Drop(num_args + 2); | |
| 688 // Pop returned code object into R0 (null if not found). | |
| 689 // Restore arguments descriptor array and IC data array. | |
| 690 __ PopList((1 << R0) | (1 << R4) | (1 << R5)); | |
| 691 __ LeaveStubFrame(); | |
| 692 Label call_target_function; | |
| 693 __ CompareImmediate(R0, reinterpret_cast<intptr_t>(Object::null())); | |
| 694 __ b(&call_target_function, NE); | |
| 695 // NoSuchMethod or closure. | |
| 696 // Mark IC call that it may be a closure call that does not collect | |
| 697 // type feedback. | |
| 698 __ mov(IP, ShifterOperand(1)); | |
| 699 __ strb(IP, FieldAddress(R5, ICData::is_closure_call_offset())); | |
| 700 __ Branch(&StubCode::InstanceFunctionLookupLabel()); | |
| 701 | |
| 702 __ Bind(&found); | |
| 703 // R6: Pointer to an IC data check group. | |
| 704 const intptr_t target_offset = ICData::TargetIndexFor(num_args) * kWordSize; | |
| 705 const intptr_t count_offset = ICData::CountIndexFor(num_args) * kWordSize; | |
| 706 __ LoadFromOffset(kLoadWord, R0, R6, target_offset); | |
| 707 __ LoadFromOffset(kLoadWord, R1, R6, count_offset); | |
| 708 __ adds(R1, R1, ShifterOperand(Smi::RawValue(1))); | |
| 709 __ StoreToOffset(kStoreWord, R1, R6, count_offset); | |
| 710 __ b(&call_target_function, VC); // No overflow. | |
| 711 __ LoadImmediate(R1, Smi::RawValue(Smi::kMaxValue)); | |
| 712 __ StoreToOffset(kStoreWord, R1, R6, count_offset); | |
| 713 | |
| 714 __ Bind(&call_target_function); | |
| 715 // R0: Target function. | |
| 716 __ ldr(R0, FieldAddress(R0, Function::code_offset())); | |
| 717 __ ldr(R0, FieldAddress(R0, Code::instructions_offset())); | |
| 718 __ AddImmediate(R0, Instructions::HeaderSize() - kHeapObjectTag); | |
| 719 __ bx(R0); | |
| 720 | |
| 721 // Instance in R0, return its class-id in R0 as Smi. | |
| 722 __ Bind(&get_class_id_as_smi); | |
| 723 Label not_smi; | |
| 724 // Test if Smi -> load Smi class for comparison. | |
| 725 __ tst(R0, ShifterOperand(kSmiTagMask)); | |
| 726 __ mov(R0, ShifterOperand(Smi::RawValue(kSmiCid)), EQ); | |
| 727 __ bx(LR, EQ); | |
| 728 __ LoadClassId(R0, R0); | |
| 729 __ SmiTag(R0); | |
| 730 __ bx(LR); | |
| 731 } | |
| 732 | |
| 733 | |
| 734 // Use inline cache data array to invoke the target or continue in inline | |
| 735 // cache miss handler. Stub for 1-argument check (receiver class). | |
| 736 // LR: Return address. | |
| 737 // R5: Inline cache data object. | |
| 738 // R4: Arguments descriptor array. | |
| 739 // Inline cache data object structure: | |
| 740 // 0: function-name | |
| 741 // 1: N, number of arguments checked. | |
| 742 // 2 .. (length - 1): group of checks, each check containing: | |
| 743 // - N classes. | |
| 744 // - 1 target function. | |
| 390 void StubCode::GenerateOneArgCheckInlineCacheStub(Assembler* assembler) { | 745 void StubCode::GenerateOneArgCheckInlineCacheStub(Assembler* assembler) { |
| 391 __ Unimplemented("GenerateOneArgCheckInlineCacheStub stub"); | 746 GenerateUsageCounterIncrement(assembler, R6); |
| 747 GenerateNArgsCheckInlineCacheStub(assembler, 1); | |
| 392 } | 748 } |
| 393 | 749 |
| 394 | 750 |
| 395 void StubCode::GenerateTwoArgsCheckInlineCacheStub(Assembler* assembler) { | 751 void StubCode::GenerateTwoArgsCheckInlineCacheStub(Assembler* assembler) { |
| 396 __ Unimplemented("GenerateTwoArgsCheckInlineCacheStub stub"); | 752 GenerateUsageCounterIncrement(assembler, R6); |
| 753 GenerateNArgsCheckInlineCacheStub(assembler, 2); | |
| 397 } | 754 } |
| 398 | 755 |
| 399 | 756 |
| 400 void StubCode::GenerateThreeArgsCheckInlineCacheStub(Assembler* assembler) { | 757 void StubCode::GenerateThreeArgsCheckInlineCacheStub(Assembler* assembler) { |
| 401 __ Unimplemented("GenerateThreeArgsCheckInlineCacheStub stub"); | 758 GenerateUsageCounterIncrement(assembler, R6); |
| 402 } | 759 GenerateNArgsCheckInlineCacheStub(assembler, 3); |
| 403 | 760 } |
| 404 | 761 |
| 762 | |
| 405 void StubCode::GenerateOneArgOptimizedCheckInlineCacheStub( | 763 void StubCode::GenerateOneArgOptimizedCheckInlineCacheStub( |
| 406 Assembler* assembler) { | 764 Assembler* assembler) { |
| 407 GenerateOptimizedUsageCounterIncrement(assembler); | 765 GenerateOptimizedUsageCounterIncrement(assembler); |
| 408 GenerateNArgsCheckInlineCacheStub(assembler, 1); | 766 GenerateNArgsCheckInlineCacheStub(assembler, 1); |
| 409 } | 767 } |
| 410 | 768 |
| 411 | 769 |
| 412 void StubCode::GenerateTwoArgsOptimizedCheckInlineCacheStub( | 770 void StubCode::GenerateTwoArgsOptimizedCheckInlineCacheStub( |
| 413 Assembler* assembler) { | 771 Assembler* assembler) { |
| 414 GenerateOptimizedUsageCounterIncrement(assembler); | 772 GenerateOptimizedUsageCounterIncrement(assembler); |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 441 void StubCode::GenerateBreakpointReturnStub(Assembler* assembler) { | 799 void StubCode::GenerateBreakpointReturnStub(Assembler* assembler) { |
| 442 __ Unimplemented("BreakpointReturn stub"); | 800 __ Unimplemented("BreakpointReturn stub"); |
| 443 } | 801 } |
| 444 | 802 |
| 445 | 803 |
| 446 void StubCode::GenerateBreakpointDynamicStub(Assembler* assembler) { | 804 void StubCode::GenerateBreakpointDynamicStub(Assembler* assembler) { |
| 447 __ Unimplemented("BreakpointDynamic stub"); | 805 __ Unimplemented("BreakpointDynamic stub"); |
| 448 } | 806 } |
| 449 | 807 |
| 450 | 808 |
| 451 void StubCode::GenerateSubtype1TestCacheStub(Assembler* assembler) { | 809 // Used to check class and type arguments. Arguments passed in registers: |
| 452 __ Unimplemented("Subtype1TestCache Stub"); | 810 // LR: return address. |
| 811 // R0: instance (must be preserved). | |
| 812 // R1: instantiator type arguments or NULL. | |
| 813 // R2: cache array. | |
| 814 // Result in R1: null -> not found, otherwise result (true or false). | |
| 815 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) { | |
| 816 ASSERT((1 <= n) && (n <= 3)); | |
| 817 if (n > 1) { | |
| 818 // Get instance type arguments. | |
| 819 __ LoadClass(R3, R0, R4); | |
| 820 // Compute instance type arguments into R4. | |
| 821 Label has_no_type_arguments; | |
| 822 __ LoadImmediate(R4, reinterpret_cast<intptr_t>(Object::null())); | |
| 823 __ ldr(R5, FieldAddress(R3, | |
| 824 Class::type_arguments_field_offset_in_words_offset())); | |
| 825 __ CompareImmediate(R5, Class::kNoTypeArguments); | |
| 826 __ b(&has_no_type_arguments, EQ); | |
| 827 __ add(R5, R0, ShifterOperand(R5, LSL, 2)); | |
| 828 __ ldr(R4, FieldAddress(R5, 0)); | |
| 829 __ Bind(&has_no_type_arguments); | |
| 830 } | |
| 831 __ LoadClassId(R3, R0); | |
| 832 // R0: instance. | |
| 833 // R1: instantiator type arguments or NULL. | |
| 834 // R2: SubtypeTestCache. | |
| 835 // R3: instance class id. | |
| 836 // R4: instance type arguments (null if none), used only if n > 1. | |
| 837 __ ldr(R2, FieldAddress(R2, SubtypeTestCache::cache_offset())); | |
| 838 __ AddImmediate(R2, Array::data_offset() - kHeapObjectTag); | |
| 839 | |
| 840 Label loop, found, not_found, next_iteration; | |
| 841 // R2: Entry start. | |
| 842 // R3: instance class id. | |
| 843 // R4: instance type arguments. | |
| 844 __ SmiTag(R3); | |
| 845 __ Bind(&loop); | |
| 846 __ ldr(R5, Address(R2, kWordSize * SubtypeTestCache::kInstanceClassId)); | |
| 847 __ CompareImmediate(R5, reinterpret_cast<intptr_t>(Object::null())); | |
| 848 __ b(¬_found, EQ); | |
| 849 __ cmp(R5, ShifterOperand(R3)); | |
| 850 if (n == 1) { | |
| 851 __ b(&found, EQ); | |
| 852 } else { | |
| 853 __ b(&next_iteration, NE); | |
| 854 __ ldr(R5, | |
| 855 Address(R2, kWordSize * SubtypeTestCache::kInstanceTypeArguments)); | |
| 856 __ cmp(R5, ShifterOperand(R4)); | |
| 857 if (n == 2) { | |
| 858 __ b(&found, EQ); | |
| 859 } else { | |
| 860 __ b(&next_iteration, NE); | |
| 861 __ ldr(R5, Address(R2, kWordSize * | |
| 862 SubtypeTestCache::kInstantiatorTypeArguments)); | |
| 863 __ cmp(R5, ShifterOperand(R1)); | |
| 864 __ b(&found, EQ); | |
| 865 } | |
| 866 } | |
| 867 __ Bind(&next_iteration); | |
| 868 __ AddImmediate(R2, kWordSize * SubtypeTestCache::kTestEntryLength); | |
| 869 __ b(&loop); | |
| 870 // Fall through to not found. | |
| 871 __ Bind(¬_found); | |
| 872 __ LoadImmediate(R1, reinterpret_cast<intptr_t>(Object::null())); | |
| 873 __ Ret(); | |
| 874 | |
| 875 __ Bind(&found); | |
| 876 __ ldr(R1, Address(R2, kWordSize * SubtypeTestCache::kTestResult)); | |
| 877 __ Ret(); | |
| 453 } | 878 } |
| 454 | 879 |
| 455 | 880 |
| 456 void StubCode::GenerateSubtype2TestCacheStub(Assembler* assembler) { | 881 // Used to check class and type arguments. Arguments passed in registers: |
| 457 __ Unimplemented("Subtype2TestCache Stub"); | 882 // LR: return address. |
| 883 // R0: instance (must be preserved). | |
| 884 // R1: instantiator type arguments or NULL. | |
| 885 // R2: cache array. | |
| 886 // Result in R1: null -> not found, otherwise result (true or false). | |
| 887 void StubCode::GenerateSubtype1TestCacheStub(Assembler* assembler) { | |
| 888 GenerateSubtypeNTestCacheStub(assembler, 1); | |
| 458 } | 889 } |
| 459 | 890 |
| 460 | 891 |
| 461 void StubCode::GenerateSubtype3TestCacheStub(Assembler* assembler) { | 892 // Used to check class and type arguments. Arguments passed in registers: |
| 462 __ Unimplemented("Subtype3TestCache Stub"); | 893 // LR: return address. |
| 894 // R0: instance (must be preserved). | |
| 895 // R1: instantiator type arguments or NULL. | |
| 896 // R2: cache array. | |
| 897 // Result in R1: null -> not found, otherwise result (true or false). | |
| 898 void StubCode::GenerateSubtype2TestCacheStub(Assembler* assembler) { | |
| 899 GenerateSubtypeNTestCacheStub(assembler, 2); | |
| 463 } | 900 } |
| 464 | 901 |
| 465 | 902 |
| 903 // Used to check class and type arguments. Arguments passed in registers: | |
| 904 // LR: return address. | |
| 905 // R0: instance (must be preserved). | |
| 906 // R1: instantiator type arguments or NULL. | |
| 907 // R2: cache array. | |
| 908 // Result in R1: null -> not found, otherwise result (true or false). | |
| 909 void StubCode::GenerateSubtype3TestCacheStub(Assembler* assembler) { | |
| 910 GenerateSubtypeNTestCacheStub(assembler, 3); | |
| 911 } | |
| 912 | |
| 913 | |
| 466 // Return the current stack pointer address, used to stack alignment | 914 // Return the current stack pointer address, used to stack alignment |
| 467 // checks. | 915 // checks. |
| 468 void StubCode::GenerateGetStackPointerStub(Assembler* assembler) { | 916 void StubCode::GenerateGetStackPointerStub(Assembler* assembler) { |
| 469 __ Unimplemented("GetStackPointer Stub"); | 917 __ Unimplemented("GetStackPointer Stub"); |
| 470 } | 918 } |
| 471 | 919 |
| 472 | 920 |
| 473 // Jump to the exception handler. | 921 // Jump to the exception handler. |
| 474 // No Result. | 922 // No Result. |
| 475 void StubCode::GenerateJumpToExceptionHandlerStub(Assembler* assembler) { | 923 void StubCode::GenerateJumpToExceptionHandlerStub(Assembler* assembler) { |
| 476 __ Unimplemented("JumpToExceptionHandler Stub"); | 924 __ Unimplemented("JumpToExceptionHandler Stub"); |
| 477 } | 925 } |
| 478 | 926 |
| 479 | 927 |
| 480 // Jump to the error handler. | 928 // Jump to the error handler. |
| 481 // No Result. | 929 // No Result. |
| 482 void StubCode::GenerateJumpToErrorHandlerStub(Assembler* assembler) { | 930 void StubCode::GenerateJumpToErrorHandlerStub(Assembler* assembler) { |
| 483 __ Unimplemented("JumpToErrorHandler Stub"); | 931 __ Unimplemented("JumpToErrorHandler Stub"); |
| 484 } | 932 } |
| 485 | 933 |
| 486 | 934 |
| 935 // Implements equality operator when one of the arguments is null | |
| 936 // (identity check) and updates ICData if necessary. | |
| 937 // LR: return address. | |
| 938 // R6: ICData. | |
|
zra
2013/04/03 17:39:47
R5?
regis
2013/04/03 20:06:07
Since the stub is unimplemented, the comment is me
| |
| 939 // R1: left operand. | |
| 940 // R0: right operand. | |
| 941 // Return result in R0. | |
| 942 // TODO(srdjan): Move to VM stubs once Boolean objects become VM objects. | |
| 487 void StubCode::GenerateEqualityWithNullArgStub(Assembler* assembler) { | 943 void StubCode::GenerateEqualityWithNullArgStub(Assembler* assembler) { |
| 488 __ Unimplemented("EqualityWithNullArg stub"); | 944 __ Unimplemented("EqualityWithNullArg Stub"); |
| 945 #if 0 | |
|
zra
2013/04/03 17:39:47
Maybe rm from the CL?
regis
2013/04/03 20:06:07
Oops. Removed.
| |
| 946 static const intptr_t kNumArgsTested = 2; | |
| 947 #if defined(DEBUG) | |
| 948 { Label ok; | |
| 949 __ movl(EAX, FieldAddress(ECX, ICData::num_args_tested_offset())); | |
| 950 __ cmpl(EAX, Immediate(kNumArgsTested)); | |
| 951 __ j(EQUAL, &ok, Assembler::kNearJump); | |
| 952 __ Stop("Incorrect ICData for equality"); | |
| 953 __ Bind(&ok); | |
| 954 } | |
| 955 #endif // DEBUG | |
| 956 // Check IC data, update if needed. | |
| 957 // EBX: IC data object (preserved). | |
| 958 __ movl(EBX, FieldAddress(ECX, ICData::ic_data_offset())); | |
| 959 // EBX: ic_data_array with check entries: classes and target functions. | |
| 960 __ leal(EBX, FieldAddress(EBX, Array::data_offset())); | |
| 961 // EBX: points directly to the first ic data array element. | |
| 962 | |
| 963 Label get_class_id_as_smi, no_match, loop, compute_result, found; | |
| 964 __ Bind(&loop); | |
| 965 // Check left. | |
| 966 __ movl(EAX, Address(ESP, 2 * kWordSize)); | |
| 967 __ call(&get_class_id_as_smi); | |
| 968 __ movl(EDI, Address(EBX, 0 * kWordSize)); | |
| 969 __ cmpl(EAX, EDI); // Class id match? | |
| 970 __ j(NOT_EQUAL, &no_match, Assembler::kNearJump); | |
| 971 // Check right. | |
| 972 __ movl(EAX, Address(ESP, 1 * kWordSize)); | |
| 973 __ call(&get_class_id_as_smi); | |
| 974 __ movl(EDI, Address(EBX, 1 * kWordSize)); | |
| 975 __ cmpl(EAX, EDI); // Class id match? | |
| 976 __ j(EQUAL, &found, Assembler::kNearJump); | |
| 977 __ Bind(&no_match); | |
| 978 // Next check group. | |
| 979 __ addl(EBX, Immediate( | |
| 980 kWordSize * ICData::TestEntryLengthFor(kNumArgsTested))); | |
| 981 __ cmpl(EDI, Immediate(Smi::RawValue(kIllegalCid))); // Done? | |
| 982 __ j(NOT_EQUAL, &loop, Assembler::kNearJump); | |
| 983 Label update_ic_data; | |
| 984 __ jmp(&update_ic_data); | |
| 985 | |
| 986 __ Bind(&found); | |
| 987 const intptr_t count_offset = | |
| 988 ICData::CountIndexFor(kNumArgsTested) * kWordSize; | |
| 989 __ addl(Address(EBX, count_offset), Immediate(Smi::RawValue(1))); | |
| 990 __ j(NO_OVERFLOW, &compute_result); | |
| 991 __ movl(Address(EBX, count_offset), | |
| 992 Immediate(Smi::RawValue(Smi::kMaxValue))); | |
| 993 | |
| 994 __ Bind(&compute_result); | |
| 995 Label true_label; | |
| 996 __ movl(EAX, Address(ESP, 1 * kWordSize)); | |
| 997 __ cmpl(EAX, Address(ESP, 2 * kWordSize)); | |
| 998 __ j(EQUAL, &true_label, Assembler::kNearJump); | |
| 999 __ LoadObject(EAX, Bool::False()); | |
| 1000 __ ret(); | |
| 1001 __ Bind(&true_label); | |
| 1002 __ LoadObject(EAX, Bool::True()); | |
| 1003 __ ret(); | |
| 1004 | |
| 1005 __ Bind(&get_class_id_as_smi); | |
| 1006 Label not_smi; | |
| 1007 // Test if Smi -> load Smi class for comparison. | |
| 1008 __ testl(EAX, Immediate(kSmiTagMask)); | |
| 1009 __ j(NOT_ZERO, ¬_smi, Assembler::kNearJump); | |
| 1010 __ movl(EAX, Immediate(Smi::RawValue(kSmiCid))); | |
| 1011 __ ret(); | |
| 1012 | |
| 1013 __ Bind(¬_smi); | |
| 1014 __ LoadClassId(EAX, EAX); | |
| 1015 __ SmiTag(EAX); | |
| 1016 __ ret(); | |
| 1017 | |
| 1018 __ Bind(&update_ic_data); | |
| 1019 | |
| 1020 // ECX: ICData | |
| 1021 __ movl(EAX, Address(ESP, 1 * kWordSize)); | |
| 1022 __ movl(EDI, Address(ESP, 2 * kWordSize)); | |
| 1023 __ EnterStubFrame(); | |
| 1024 __ pushl(EDI); // arg 0 | |
| 1025 __ pushl(EAX); // arg 1 | |
| 1026 __ PushObject(Symbols::EqualOperator()); // Target's name. | |
| 1027 __ pushl(ECX); // ICData | |
| 1028 __ CallRuntime(kUpdateICDataTwoArgsRuntimeEntry); | |
| 1029 __ Drop(4); | |
| 1030 __ LeaveFrame(); | |
| 1031 | |
| 1032 __ jmp(&compute_result, Assembler::kNearJump); | |
| 1033 #endif | |
| 489 } | 1034 } |
| 490 | 1035 |
| 491 | 1036 |
| 492 void StubCode::GenerateOptimizeFunctionStub(Assembler* assembler) { | 1037 void StubCode::GenerateOptimizeFunctionStub(Assembler* assembler) { |
| 493 __ Unimplemented("OptimizeFunction stub"); | 1038 __ Unimplemented("OptimizeFunction stub"); |
| 494 } | 1039 } |
| 495 | 1040 |
| 496 | 1041 |
| 1042 DECLARE_LEAF_RUNTIME_ENTRY(intptr_t, | |
| 1043 BigintCompare, | |
| 1044 RawBigint* left, | |
| 1045 RawBigint* right); | |
| 1046 | |
| 1047 | |
| 1048 // Does identical check (object references are equal or not equal) with special | |
| 1049 // checks for boxed numbers. | |
| 1050 // LR: return address. | |
| 1051 // SP + 4: left operand. | |
| 1052 // SP + 0: right operand. | |
| 1053 // Return Zero condition flag set if equal. | |
| 1054 // Note: A Mint cannot contain a value that would fit in Smi, a Bigint | |
| 1055 // cannot contain a value that fits in Mint or Smi. | |
| 497 void StubCode::GenerateIdenticalWithNumberCheckStub(Assembler* assembler) { | 1056 void StubCode::GenerateIdenticalWithNumberCheckStub(Assembler* assembler) { |
| 498 __ Unimplemented("IdenticalWithNumberCheck stub"); | 1057 const Register temp = R2; |
| 1058 const Register left = R1; | |
| 1059 const Register right = R0; | |
| 1060 // Preserve left, right and temp. | |
| 1061 __ PushList((1 << R0) | (1 << R1) | (1 << R2)); | |
| 1062 // TOS + 4: left argument. | |
| 1063 // TOS + 3: right argument. | |
| 1064 // TOS + 2: saved temp | |
| 1065 // TOS + 1: saved left | |
| 1066 // TOS + 0: saved right | |
| 1067 __ ldr(left, Address(SP, 4 * kWordSize)); | |
| 1068 __ ldr(right, Address(SP, 3 * kWordSize)); | |
| 1069 Label reference_compare, done, check_mint, check_bigint; | |
| 1070 // If any of the arguments is Smi do reference compare. | |
| 1071 __ tst(left, ShifterOperand(kSmiTagMask)); | |
| 1072 __ b(&reference_compare, EQ); | |
| 1073 __ tst(right, ShifterOperand(kSmiTagMask)); | |
| 1074 __ b(&reference_compare, EQ); | |
| 1075 | |
| 1076 // Value compare for two doubles. | |
| 1077 __ CompareClassId(left, kDoubleCid, temp); | |
| 1078 __ b(&check_mint, NE); | |
| 1079 __ CompareClassId(right, kDoubleCid, temp); | |
| 1080 __ b(&done, NE); | |
| 1081 | |
| 1082 // Double values bitwise compare. | |
| 1083 __ ldr(temp, FieldAddress(left, Double::value_offset() + 0 * kWordSize)); | |
| 1084 __ ldr(IP, FieldAddress(right, Double::value_offset() + 0 * kWordSize)); | |
| 1085 __ cmp(temp, ShifterOperand(IP)); | |
| 1086 __ b(&done, NE); | |
| 1087 __ ldr(temp, FieldAddress(left, Double::value_offset() + 1 * kWordSize)); | |
| 1088 __ ldr(IP, FieldAddress(right, Double::value_offset() + 1 * kWordSize)); | |
| 1089 __ cmp(temp, ShifterOperand(IP)); | |
| 1090 __ b(&done); | |
| 1091 | |
| 1092 __ Bind(&check_mint); | |
| 1093 __ CompareClassId(left, kMintCid, temp); | |
| 1094 __ b(&check_bigint, NE); | |
| 1095 __ CompareClassId(right, kMintCid, temp); | |
| 1096 __ b(&done, NE); | |
| 1097 __ ldr(temp, FieldAddress(left, Mint::value_offset() + 0 * kWordSize)); | |
| 1098 __ ldr(IP, FieldAddress(right, Mint::value_offset() + 0 * kWordSize)); | |
| 1099 __ cmp(temp, ShifterOperand(IP)); | |
| 1100 __ b(&done, NE); | |
| 1101 __ ldr(temp, FieldAddress(left, Mint::value_offset() + 1 * kWordSize)); | |
| 1102 __ ldr(IP, FieldAddress(right, Mint::value_offset() + 1 * kWordSize)); | |
| 1103 __ cmp(temp, ShifterOperand(IP)); | |
| 1104 __ b(&done); | |
| 1105 | |
| 1106 __ Bind(&check_bigint); | |
| 1107 __ CompareClassId(left, kBigintCid, temp); | |
| 1108 __ b(&reference_compare, NE); | |
| 1109 __ CompareClassId(right, kBigintCid, temp); | |
| 1110 __ b(&done, NE); | |
| 1111 __ EnterStubFrame(0); | |
| 1112 __ ReserveAlignedFrameSpace(2 * kWordSize); | |
| 1113 __ stm(IA, SP, (1 << R0) | (1 << R1)); | |
| 1114 __ CallRuntime(kBigintCompareRuntimeEntry); | |
| 1115 // Result in R0, 0 means equal. | |
| 1116 __ LeaveStubFrame(); | |
| 1117 __ cmp(R0, ShifterOperand(0)); | |
| 1118 __ b(&done); | |
| 1119 | |
| 1120 __ Bind(&reference_compare); | |
| 1121 __ cmp(left, ShifterOperand(right)); | |
| 1122 __ Bind(&done); | |
| 1123 __ PopList((1 << R0) | (1 << R1) | (1 << R2)); | |
| 1124 __ Ret(); | |
| 499 } | 1125 } |
| 500 | 1126 |
| 501 } // namespace dart | 1127 } // namespace dart |
| 502 | 1128 |
| 503 #endif // defined TARGET_ARCH_ARM | 1129 #endif // defined TARGET_ARCH_ARM |
| OLD | NEW |