v8/test/unittests/assembler/turbo-assembler-arm-unittest.cc
Pierre Langlois 3f1a59f47f [arm][arm64] Do not allocate temp registers for the write barrier.
Improve code generation for stores with write barriers slightly by using the
assembler's dedicated scratch registers (x16 and x17 on Arm64, ip on Arm)
instead of allocating temporaries.

To do this, we've done two things:

  - Use ip as a scratch register when loading page flags.

  - TurboAssembler::CallRecordWriteStub() now takes the offset of the slot
    that's written to rather than its address, removing the need to allocate a
    temporary register for it.

In essence, we've gone from:

```
;; Do the store.
stur x19, [x9, #15]
;; Check *destination* object page flags and jump out-of-line.
and x4, x9, #0xfffffffffff80000
ldr x4, [x4, #8]
tbnz x4, #2, #+0x1e7c
|     ;; Check *source* object page flags.
| `-> and x4, x19, #0xfffffffffff80000
|     ldr x4, [xM, #8]
|,--- tbz x4, #1, #-0x1e80
|     ;; Compute address of slot.
|     add x5, x9, #0xf (15)
|     ;; Setup arguments to RecordWrite
|     stp x2, x3, [sp, #-32]!
|     stp x4, lr, [sp, #16]
|     stp x0, x1, [sp, #-16]!
|     mov x0, x9 ;; Object address in x9
|     mov x1, x5 ;; Slot address in x5
|     movz x2, #0x0
|     movz x3, #0x100000000
|     ;; Call RecordWrite
|     ldr x16, pc+2056
|     blr x16
```

Which allocates x4 and x5 as temporaries.

To:

```
stur x19, [x9, #15]
and x16, x9, #0xfffffffffff80000 ;; Using x16 instead of allocating x4.
ldr x16, [x16, #8]
tbnz x16, #2, #+0x1e7c
| `-> and x16, x19, #0xfffffffffff80000
|     ldr x16, [xM, #8]
|,--- tbz x16, #1, #-0x1e80
|     stp x2, x3, [sp, #-32]!
|     stp x4, lr, [sp, #16]
|     stp x0, x1, [sp, #-16]!
|     mov x0, x9            ;; Object address still in x9.
|     add x1, x9, #0xf (15) ;; Compute the slot address directly.
|     movz x2, #0x0
|     movz x3, #0x100000000
|     ldr x16, pc+2056
|     blr x16
```

Finally, `RecordWriteField()` does not need an extra scratch register anymore.

Change-Id: Icb71310e7b8ab1ca83ced250851456166b337d00
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1505793
Commit-Queue: Pierre Langlois <pierre.langlois@arm.com>
Reviewed-by: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61153}
2019-05-02 11:19:00 +00:00

189 lines
6.0 KiB
C++

// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/arm/assembler-arm-inl.h"
#include "src/macro-assembler.h"
#include "src/ostreams.h"
#include "src/simulator.h"
#include "test/common/assembler-tester.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest-support.h"
namespace v8 {
namespace internal {
#define __ tasm.
// If we are running on android and the output is not redirected (i.e. ends up
// in the android log) then we cannot find the error message in the output. This
// macro just returns the empty string in that case.
#if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT)
#define ERROR_MESSAGE(msg) ""
#else
#define ERROR_MESSAGE(msg) msg
#endif
// Test the x64 assembler by compiling some simple functions into
// a buffer and executing them. These tests do not initialize the
// V8 library, create a context, or use any V8 objects.
class TurboAssemblerTest : public TestWithIsolate {};
TEST_F(TurboAssemblerTest, TestHardAbort) {
auto buffer = AllocateAssemblerBuffer();
TurboAssembler tasm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
buffer->CreateView());
__ set_abort_hard(true);
__ Abort(AbortReason::kNoReason);
CodeDesc desc;
tasm.GetCode(nullptr, &desc);
buffer->MakeExecutable();
// We need an isolate here to execute in the simulator.
auto f = GeneratedCode<void>::FromBuffer(isolate(), buffer->start());
ASSERT_DEATH_IF_SUPPORTED({ f.Call(); }, ERROR_MESSAGE("abort: no reason"));
}
TEST_F(TurboAssemblerTest, TestCheck) {
auto buffer = AllocateAssemblerBuffer();
TurboAssembler tasm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
buffer->CreateView());
__ set_abort_hard(true);
// Fail if the first parameter is 17.
__ Move32BitImmediate(r1, Operand(17));
__ cmp(r0, r1); // 1st parameter is in {r0}.
__ Check(Condition::ne, AbortReason::kNoReason);
__ Ret();
CodeDesc desc;
tasm.GetCode(nullptr, &desc);
buffer->MakeExecutable();
// We need an isolate here to execute in the simulator.
auto f = GeneratedCode<void, int>::FromBuffer(isolate(), buffer->start());
f.Call(0);
f.Call(18);
ASSERT_DEATH_IF_SUPPORTED({ f.Call(17); }, ERROR_MESSAGE("abort: no reason"));
}
struct MoveObjectAndSlotTestCase {
const char* comment;
Register dst_object;
Register dst_slot;
Register object;
Register offset_register = no_reg;
};
const MoveObjectAndSlotTestCase kMoveObjectAndSlotTestCases[] = {
{"no overlap", r0, r1, r2},
{"no overlap", r0, r1, r2, r3},
{"object == dst_object", r2, r1, r2},
{"object == dst_object", r2, r1, r2, r3},
{"object == dst_slot", r1, r2, r2},
{"object == dst_slot", r1, r2, r2, r3},
{"offset == dst_object", r0, r1, r2, r0},
{"offset == dst_object && object == dst_slot", r0, r1, r1, r0},
{"offset == dst_slot", r0, r1, r2, r1},
{"offset == dst_slot && object == dst_object", r0, r1, r0, r1}};
// Make sure we include offsets that cannot be encoded in an add instruction.
const int kOffsets[] = {0, 42, kMaxRegularHeapObjectSize, 0x101001};
template <typename T>
class TurboAssemblerTestWithParam : public TurboAssemblerTest,
public ::testing::WithParamInterface<T> {};
using TurboAssemblerTestMoveObjectAndSlot =
TurboAssemblerTestWithParam<MoveObjectAndSlotTestCase>;
TEST_P(TurboAssemblerTestMoveObjectAndSlot, MoveObjectAndSlot) {
const MoveObjectAndSlotTestCase test_case = GetParam();
TRACED_FOREACH(int32_t, offset, kOffsets) {
auto buffer = AllocateAssemblerBuffer();
TurboAssembler tasm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
buffer->CreateView());
__ Push(r0);
__ Move(test_case.object, r1);
Register src_object = test_case.object;
Register dst_object = test_case.dst_object;
Register dst_slot = test_case.dst_slot;
Operand offset_operand(0);
if (test_case.offset_register == no_reg) {
offset_operand = Operand(offset);
} else {
__ mov(test_case.offset_register, Operand(offset));
offset_operand = Operand(test_case.offset_register);
}
std::stringstream comment;
comment << "-- " << test_case.comment << ": MoveObjectAndSlot("
<< dst_object << ", " << dst_slot << ", " << src_object << ", ";
if (test_case.offset_register == no_reg) {
comment << "#" << offset;
} else {
comment << test_case.offset_register;
}
comment << ") --";
__ RecordComment(comment.str().c_str());
__ MoveObjectAndSlot(dst_object, dst_slot, src_object, offset_operand);
__ RecordComment("--");
// The `result` pointer was saved on the stack.
UseScratchRegisterScope temps(&tasm);
Register scratch = temps.Acquire();
__ Pop(scratch);
__ str(dst_object, MemOperand(scratch));
__ str(dst_slot, MemOperand(scratch, kSystemPointerSize));
__ Ret();
CodeDesc desc;
tasm.GetCode(nullptr, &desc);
if (FLAG_print_code) {
Handle<Code> code =
Factory::CodeBuilder(isolate(), desc, Code::STUB).Build();
StdoutStream os;
code->Print(os);
}
buffer->MakeExecutable();
// We need an isolate here to execute in the simulator.
auto f = GeneratedCode<void, byte**, byte*>::FromBuffer(isolate(),
buffer->start());
byte* object = new byte[offset];
byte* result[] = {nullptr, nullptr};
f.Call(result, object);
// The first element must be the address of the object, and the second the
// slot addressed by `offset`.
EXPECT_EQ(result[0], &object[0]);
EXPECT_EQ(result[1], &object[offset]);
delete[] object;
}
}
INSTANTIATE_TEST_SUITE_P(TurboAssemblerTest,
TurboAssemblerTestMoveObjectAndSlot,
::testing::ValuesIn(kMoveObjectAndSlotTestCases));
#undef __
#undef ERROR_MESSAGE
} // namespace internal
} // namespace v8