test-icache.cc 6.62 KB
Newer Older
1 2 3 4
// 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.

5 6
#include "src/codegen/assembler-inl.h"
#include "src/codegen/macro-assembler-inl.h"
7
#include "src/execution/simulator.h"
8
#include "src/handles/handles-inl.h"
9 10 11 12 13
#include "test/cctest/cctest.h"
#include "test/common/assembler-tester.h"

namespace v8 {
namespace internal {
14
namespace test_icache {
15 16 17 18 19 20

using F0 = int(int);

#define __ masm.

static constexpr int kNumInstr = 100;
21 22
static constexpr int kNumIterations = 5;
static constexpr int kBufferSize = 8 * KB;
23

24 25
static void FloodWithInc(Isolate* isolate, TestingAssemblerBuffer* buffer) {
  MacroAssembler masm(isolate, CodeObjectRequired::kYes, buffer->CreateView());
26
#if V8_TARGET_ARCH_IA32
27
  __ mov(eax, Operand(esp, kSystemPointerSize));
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  for (int i = 0; i < kNumInstr; ++i) {
    __ add(eax, Immediate(1));
  }
#elif V8_TARGET_ARCH_X64
  __ movl(rax, arg_reg_1);
  for (int i = 0; i < kNumInstr; ++i) {
    __ addl(rax, Immediate(1));
  }
#elif V8_TARGET_ARCH_ARM64
  for (int i = 0; i < kNumInstr; ++i) {
    __ Add(x0, x0, Operand(1));
  }
#elif V8_TARGET_ARCH_ARM
  for (int i = 0; i < kNumInstr; ++i) {
    __ add(r0, r0, Operand(1));
  }
#elif V8_TARGET_ARCH_MIPS
  __ mov(v0, a0);
  for (int i = 0; i < kNumInstr; ++i) {
    __ Addu(v0, v0, Operand(1));
  }
#elif V8_TARGET_ARCH_MIPS64
  __ mov(v0, a0);
  for (int i = 0; i < kNumInstr; ++i) {
    __ Addu(v0, v0, Operand(1));
  }
54 55 56 57 58 59 60 61
#elif V8_TARGET_ARCH_PPC
  for (int i = 0; i < kNumInstr; ++i) {
    __ addi(r3, r3, Operand(1));
  }
#elif V8_TARGET_ARCH_S390
  for (int i = 0; i < kNumInstr; ++i) {
    __ agfi(r2, Operand(1));
  }
62 63 64 65
#else
#error Unsupported architecture
#endif
  __ Ret();
66 67
  CodeDesc desc;
  masm.GetCode(isolate, &desc);
68 69
}

70 71
static void FloodWithNop(Isolate* isolate, TestingAssemblerBuffer* buffer) {
  MacroAssembler masm(isolate, CodeObjectRequired::kYes, buffer->CreateView());
72
#if V8_TARGET_ARCH_IA32
73
  __ mov(eax, Operand(esp, kSystemPointerSize));
74 75 76 77 78 79 80 81 82 83 84
#elif V8_TARGET_ARCH_X64
  __ movl(rax, arg_reg_1);
#elif V8_TARGET_ARCH_MIPS
  __ mov(v0, a0);
#elif V8_TARGET_ARCH_MIPS64
  __ mov(v0, a0);
#endif
  for (int i = 0; i < kNumInstr; ++i) {
    __ nop();
  }
  __ Ret();
85 86
  CodeDesc desc;
  masm.GetCode(isolate, &desc);
87 88 89 90 91 92 93
}

// Order of operation for this test case:
//   exec -> perm(RW) -> patch -> flush -> perm(RX) -> exec
TEST(TestFlushICacheOfWritable) {
  Isolate* isolate = CcTest::i_isolate();
  HandleScope handles(isolate);
94 95

  for (int i = 0; i < kNumIterations; ++i) {
96
    auto buffer = AllocateAssemblerBuffer(kBufferSize);
97 98

    // Allow calling the function from C++.
99 100 101 102 103
    auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start());

    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadWrite));
    FloodWithInc(isolate, buffer.get());
104
    FlushInstructionCache(buffer->start(), buffer->size());
105 106
    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadExecute));
107
    CHECK_EQ(23 + kNumInstr, f.Call(23));  // Call into generated code.
108 109 110
    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadWrite));
    FloodWithNop(isolate, buffer.get());
111
    FlushInstructionCache(buffer->start(), buffer->size());
112 113
    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadExecute));
114 115
    CHECK_EQ(23, f.Call(23));  // Call into generated code.
  }
116 117
}

118 119 120
#if V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_ARM64
// Note that this order of operations is not supported on ARM32/64 because on
// some older ARM32/64 kernels there is a bug which causes an access error on
121 122 123 124
// cache flush instructions to trigger access error on non-writable memory.
// See https://bugs.chromium.org/p/v8/issues/detail?id=8157
//
// Also note that this requires {kBufferSize == 8 * KB} to reproduce.
125 126 127 128
//
// The order of operations in V8 is akin to {TestFlushICacheOfWritable} above.
// It is hence OK to disable the below test on some architectures. Only the
// above test case should remain enabled on all architectures.
129 130 131 132 133
#define CONDITIONAL_TEST DISABLED_TEST
#else
#define CONDITIONAL_TEST TEST
#endif

134 135
// Order of operation for this test case:
//   exec -> perm(RW) -> patch -> perm(RX) -> flush -> exec
136
CONDITIONAL_TEST(TestFlushICacheOfExecutable) {
137 138
  Isolate* isolate = CcTest::i_isolate();
  HandleScope handles(isolate);
139 140

  for (int i = 0; i < kNumIterations; ++i) {
141
    auto buffer = AllocateAssemblerBuffer(kBufferSize);
142 143

    // Allow calling the function from C++.
144 145 146 147 148 149 150
    auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start());

    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadWrite));
    FloodWithInc(isolate, buffer.get());
    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadExecute));
151
    FlushInstructionCache(buffer->start(), buffer->size());
152
    CHECK_EQ(23 + kNumInstr, f.Call(23));  // Call into generated code.
153 154 155 156 157
    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadWrite));
    FloodWithNop(isolate, buffer.get());
    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadExecute));
158
    FlushInstructionCache(buffer->start(), buffer->size());
159 160
    CHECK_EQ(23, f.Call(23));  // Call into generated code.
  }
161 162
}

163 164
#undef CONDITIONAL_TEST

165
// Order of operation for this test case:
166
//   perm(RWX) -> exec -> patch -> flush -> exec
167 168 169
TEST(TestFlushICacheOfWritableAndExecutable) {
  Isolate* isolate = CcTest::i_isolate();
  HandleScope handles(isolate);
170 171

  for (int i = 0; i < kNumIterations; ++i) {
172
    auto buffer = AllocateAssemblerBuffer(kBufferSize);
173 174

    // Allow calling the function from C++.
175
    auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start());
176

177 178 179
    CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
                         buffer->size(), v8::PageAllocator::kReadWriteExecute));
    FloodWithInc(isolate, buffer.get());
180
    FlushInstructionCache(buffer->start(), buffer->size());
181
    CHECK_EQ(23 + kNumInstr, f.Call(23));  // Call into generated code.
182
    FloodWithNop(isolate, buffer.get());
183
    FlushInstructionCache(buffer->start(), buffer->size());
184 185
    CHECK_EQ(23, f.Call(23));  // Call into generated code.
  }
186 187 188 189
}

#undef __

190
}  // namespace test_icache
191 192
}  // namespace internal
}  // namespace v8