27e1ac1a79
Apple's upcoming arm64 devices will prevent rwx access to memory, but in turn provide a new per-thread way to switch between write and execute permissions. This patch puts that system to use for the WebAssembly subsystem. The approach relies on CodeSpaceWriteScope objects for now. That isn't optimal for background threads (which could stay in "write" mode permanently instead of toggling), but its simplicity makes it a good first step. Background: https://developer.apple.com/documentation/apple_silicon/porting_just-in-time_compilers_to_apple_silicon Bug: chromium:1117591 Change-Id: I3b60f0efd34c0fed924dfc71ee2c7805801c5d42 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2378307 Commit-Queue: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Thibaud Michaud <thibaudm@chromium.org> Cr-Commit-Position: refs/heads/master@{#69791}
201 lines
6.9 KiB
C++
201 lines
6.9 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/codegen/assembler-inl.h"
|
|
#include "src/codegen/macro-assembler-inl.h"
|
|
#include "src/execution/simulator.h"
|
|
#include "src/handles/handles-inl.h"
|
|
#include "src/wasm/code-space-access.h"
|
|
#include "test/cctest/cctest.h"
|
|
#include "test/common/assembler-tester.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace test_icache {
|
|
|
|
using F0 = int(int);
|
|
|
|
#define __ masm.
|
|
|
|
static constexpr int kNumInstr = 100;
|
|
static constexpr int kNumIterations = 5;
|
|
static constexpr int kBufferSize = 8 * KB;
|
|
|
|
static void FloodWithInc(Isolate* isolate, TestingAssemblerBuffer* buffer) {
|
|
MacroAssembler masm(isolate, CodeObjectRequired::kYes, buffer->CreateView());
|
|
#if V8_TARGET_ARCH_IA32
|
|
__ mov(eax, Operand(esp, kSystemPointerSize));
|
|
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
|
|
__ CodeEntry();
|
|
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));
|
|
}
|
|
#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
|
|
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));
|
|
}
|
|
#else
|
|
#error Unsupported architecture
|
|
#endif
|
|
__ Ret();
|
|
CodeDesc desc;
|
|
masm.GetCode(isolate, &desc);
|
|
}
|
|
|
|
static void FloodWithNop(Isolate* isolate, TestingAssemblerBuffer* buffer) {
|
|
MacroAssembler masm(isolate, CodeObjectRequired::kYes, buffer->CreateView());
|
|
#if V8_TARGET_ARCH_IA32
|
|
__ mov(eax, Operand(esp, kSystemPointerSize));
|
|
#elif V8_TARGET_ARCH_X64
|
|
__ movl(rax, arg_reg_1);
|
|
#elif V8_TARGET_ARCH_ARM64
|
|
__ CodeEntry();
|
|
#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();
|
|
CodeDesc desc;
|
|
masm.GetCode(isolate, &desc);
|
|
}
|
|
|
|
// 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);
|
|
|
|
for (int i = 0; i < kNumIterations; ++i) {
|
|
auto buffer = AllocateAssemblerBuffer(kBufferSize);
|
|
|
|
// Allow calling the function from C++.
|
|
auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start());
|
|
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
|
|
buffer->size(), v8::PageAllocator::kReadWrite));
|
|
FloodWithInc(isolate, buffer.get());
|
|
FlushInstructionCache(buffer->start(), buffer->size());
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
|
|
buffer->size(), v8::PageAllocator::kReadExecute));
|
|
CHECK_EQ(23 + kNumInstr, f.Call(23)); // Call into generated code.
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
|
|
buffer->size(), v8::PageAllocator::kReadWrite));
|
|
FloodWithNop(isolate, buffer.get());
|
|
FlushInstructionCache(buffer->start(), buffer->size());
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
|
|
buffer->size(), v8::PageAllocator::kReadExecute));
|
|
CHECK_EQ(23, f.Call(23)); // Call into generated code.
|
|
}
|
|
}
|
|
|
|
#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
|
|
// 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.
|
|
//
|
|
// 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.
|
|
#define CONDITIONAL_TEST DISABLED_TEST
|
|
#else
|
|
#define CONDITIONAL_TEST TEST
|
|
#endif
|
|
|
|
// Order of operation for this test case:
|
|
// exec -> perm(RW) -> patch -> perm(RX) -> flush -> exec
|
|
CONDITIONAL_TEST(TestFlushICacheOfExecutable) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope handles(isolate);
|
|
|
|
for (int i = 0; i < kNumIterations; ++i) {
|
|
auto buffer = AllocateAssemblerBuffer(kBufferSize);
|
|
|
|
// Allow calling the function from C++.
|
|
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));
|
|
FlushInstructionCache(buffer->start(), buffer->size());
|
|
CHECK_EQ(23 + kNumInstr, f.Call(23)); // Call into generated code.
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
|
|
buffer->size(), v8::PageAllocator::kReadWrite));
|
|
FloodWithNop(isolate, buffer.get());
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
|
|
buffer->size(), v8::PageAllocator::kReadExecute));
|
|
FlushInstructionCache(buffer->start(), buffer->size());
|
|
CHECK_EQ(23, f.Call(23)); // Call into generated code.
|
|
}
|
|
}
|
|
|
|
#undef CONDITIONAL_TEST
|
|
|
|
// Order of operation for this test case:
|
|
// perm(RWX) -> exec -> patch -> flush -> exec
|
|
TEST(TestFlushICacheOfWritableAndExecutable) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope handles(isolate);
|
|
|
|
for (int i = 0; i < kNumIterations; ++i) {
|
|
auto buffer = AllocateAssemblerBuffer(kBufferSize);
|
|
|
|
// Allow calling the function from C++.
|
|
auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start());
|
|
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
|
|
buffer->size(), v8::PageAllocator::kReadWriteExecute));
|
|
SwitchMemoryPermissionsToWritable();
|
|
FloodWithInc(isolate, buffer.get());
|
|
FlushInstructionCache(buffer->start(), buffer->size());
|
|
SwitchMemoryPermissionsToExecutable();
|
|
CHECK_EQ(23 + kNumInstr, f.Call(23)); // Call into generated code.
|
|
SwitchMemoryPermissionsToWritable();
|
|
FloodWithNop(isolate, buffer.get());
|
|
FlushInstructionCache(buffer->start(), buffer->size());
|
|
SwitchMemoryPermissionsToExecutable();
|
|
CHECK_EQ(23, f.Call(23)); // Call into generated code.
|
|
}
|
|
}
|
|
|
|
#undef __
|
|
|
|
} // namespace test_icache
|
|
} // namespace internal
|
|
} // namespace v8
|