Commit 4d8e1846 authored by Igor Sheludko's avatar Igor Sheludko Committed by V8 LUCI CQ

[rwx][mac] Introduce RwxMemoryWriteScope

... as a single bottleneck that encapsulates the semantics and
implementation of fast per-thread W^X permission switching supported
by Apple Silicon (arm64 M1).
On other architectures this class is a no-op.

Bug: v8:12797
Change-Id: Ica842ff9f843e20b7f61fd7e80591e7a1fd29771
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3586986Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79994}
parent f6a559e2
...@@ -1205,6 +1205,9 @@ filegroup( ...@@ -1205,6 +1205,9 @@ filegroup(
"src/common/assert-scope.h", "src/common/assert-scope.h",
"src/common/allow-deprecated.h", "src/common/allow-deprecated.h",
"src/common/checks.h", "src/common/checks.h",
"src/common/code-memory-access-inl.h",
"src/common/code-memory-access.cc",
"src/common/code-memory-access.h",
"src/common/high-allocation-throughput-scope.h", "src/common/high-allocation-throughput-scope.h",
"src/common/message-template.h", "src/common/message-template.h",
"src/common/operation.h", "src/common/operation.h",
......
...@@ -2750,6 +2750,8 @@ v8_header_set("v8_internal_headers") { ...@@ -2750,6 +2750,8 @@ v8_header_set("v8_internal_headers") {
"src/common/allow-deprecated.h", "src/common/allow-deprecated.h",
"src/common/assert-scope.h", "src/common/assert-scope.h",
"src/common/checks.h", "src/common/checks.h",
"src/common/code-memory-access-inl.h",
"src/common/code-memory-access.h",
"src/common/high-allocation-throughput-scope.h", "src/common/high-allocation-throughput-scope.h",
"src/common/message-template.h", "src/common/message-template.h",
"src/common/operation.h", "src/common/operation.h",
...@@ -4163,6 +4165,9 @@ v8_source_set("v8_base_without_compiler") { ...@@ -4163,6 +4165,9 @@ v8_source_set("v8_base_without_compiler") {
"src/codegen/turbo-assembler.cc", "src/codegen/turbo-assembler.cc",
"src/codegen/unoptimized-compilation-info.cc", "src/codegen/unoptimized-compilation-info.cc",
"src/common/assert-scope.cc", "src/common/assert-scope.cc",
"src/common/code-memory-access-inl.h",
"src/common/code-memory-access.cc",
"src/common/code-memory-access.h",
"src/compiler-dispatcher/lazy-compile-dispatcher.cc", "src/compiler-dispatcher/lazy-compile-dispatcher.cc",
"src/compiler-dispatcher/optimizing-compile-dispatcher.cc", "src/compiler-dispatcher/optimizing-compile-dispatcher.cc",
"src/date/date.cc", "src/date/date.cc",
......
// Copyright 2022 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.
#ifndef V8_COMMON_CODE_MEMORY_ACCESS_INL_H_
#define V8_COMMON_CODE_MEMORY_ACCESS_INL_H_
#include "src/common/code-memory-access.h"
#include "src/logging/log.h"
namespace v8 {
namespace internal {
RwxMemoryWriteScope::RwxMemoryWriteScope() { SetWritable(); }
RwxMemoryWriteScope::~RwxMemoryWriteScope() { SetExecutable(); }
#if V8_HAS_PTHREAD_JIT_WRITE_PROTECT
// Ignoring this warning is considered better than relying on
// __builtin_available.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
// static
void RwxMemoryWriteScope::SetWritable() {
if (code_space_write_nesting_level_ == 0) {
pthread_jit_write_protect_np(0);
}
code_space_write_nesting_level_++;
}
// static
void RwxMemoryWriteScope::SetExecutable() {
code_space_write_nesting_level_--;
if (code_space_write_nesting_level_ == 0) {
pthread_jit_write_protect_np(1);
}
}
#pragma clang diagnostic pop
#else // !V8_HAS_PTHREAD_JIT_WRITE_PROTECT
void RwxMemoryWriteScope::SetWritable() {}
void RwxMemoryWriteScope::SetExecutable() {}
#endif // V8_HAS_PTHREAD_JIT_WRITE_PROTECT
} // namespace internal
} // namespace v8
#endif // V8_COMMON_CODE_MEMORY_ACCESS_INL_H_
// Copyright 2022 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/common/code-memory-access.h"
namespace v8 {
namespace internal {
#if V8_HAS_PTHREAD_JIT_WRITE_PROTECT
thread_local int RwxMemoryWriteScope::code_space_write_nesting_level_ = 0;
#endif // V8_HAS_PTHREAD_JIT_WRITE_PROTECT
} // namespace internal
} // namespace v8
// Copyright 2022 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.
#ifndef V8_COMMON_CODE_MEMORY_ACCESS_H_
#define V8_COMMON_CODE_MEMORY_ACCESS_H_
#include "src/base/build_config.h"
namespace v8 {
namespace internal {
class CodePageCollectionMemoryModificationScope;
class CodePageMemoryModificationScope;
class CodeSpaceMemoryModificationScope;
namespace wasm {
class CodeSpaceWriteScope;
}
// This scope is a wrapper for APRR/MAP_JIT machinery on MacOS on ARM64
// ("Apple M1"/Apple Silicon) with respective semantics.
// See pthread_jit_write_protect_np() for details.
// On other platforms the scope is a no-op.
//
// The semantics is the following: the scope switches permissions between
// writable and executable for all the pages allocated with RWX permissions.
// Only current thread is affected. This achieves "real" W^X and it's fast.
// By default it is assumed that the state is executable.
//
// The scope is reentrant and thread safe.
class V8_NODISCARD RwxMemoryWriteScope final {
public:
V8_INLINE explicit RwxMemoryWriteScope();
V8_INLINE virtual ~RwxMemoryWriteScope();
// Disable copy constructor and copy-assignment operator, since this manages
// a resource and implicit copying of the scope can yield surprising errors.
RwxMemoryWriteScope(const RwxMemoryWriteScope&) = delete;
RwxMemoryWriteScope& operator=(const RwxMemoryWriteScope&) = delete;
private:
friend class CodePageCollectionMemoryModificationScope;
friend class CodePageMemoryModificationScope;
friend class CodeSpaceMemoryModificationScope;
friend class wasm::CodeSpaceWriteScope;
// {SetWritable} and {SetExecutable} implicitly enters/exits the scope.
// These methods are exposed only for the purpose of implementing other
// scope classes that affect executable pages permissions.
V8_INLINE static void SetWritable();
V8_INLINE static void SetExecutable();
#if V8_HAS_PTHREAD_JIT_WRITE_PROTECT
// This counter is used for supporting scope reentrance.
static thread_local int code_space_write_nesting_level_;
#endif // V8_HAS_PTHREAD_JIT_WRITE_PROTECT || defined(DEBUG)
};
} // namespace internal
} // namespace v8
#endif // V8_COMMON_CODE_MEMORY_ACCESS_H_
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "src/wasm/code-space-access.h" #include "src/wasm/code-space-access.h"
#include "src/common/code-memory-access-inl.h"
#include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-engine.h"
...@@ -36,20 +37,13 @@ CodeSpaceWriteScope::~CodeSpaceWriteScope() { ...@@ -36,20 +37,13 @@ CodeSpaceWriteScope::~CodeSpaceWriteScope() {
#if V8_HAS_PTHREAD_JIT_WRITE_PROTECT #if V8_HAS_PTHREAD_JIT_WRITE_PROTECT
// Ignoring this warning is considered better than relying on
// __builtin_available.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
// static // static
void CodeSpaceWriteScope::SetWritable() { void CodeSpaceWriteScope::SetWritable() { RwxMemoryWriteScope::SetWritable(); }
pthread_jit_write_protect_np(0);
}
// static // static
void CodeSpaceWriteScope::SetExecutable() { void CodeSpaceWriteScope::SetExecutable() {
pthread_jit_write_protect_np(1); RwxMemoryWriteScope::SetExecutable();
} }
#pragma clang diagnostic pop
// static // static
bool CodeSpaceWriteScope::SwitchingPerNativeModule() { return false; } bool CodeSpaceWriteScope::SwitchingPerNativeModule() { return false; }
......
...@@ -118,23 +118,25 @@ static void InitializeVM() { ...@@ -118,23 +118,25 @@ static void InitializeVM() {
#ifdef USE_SIMULATOR #ifdef USE_SIMULATOR
// Run tests with the simulator. // Run tests with the simulator.
#define SETUP_SIZE(buf_size) \ #define SETUP_SIZE(buf_size) \
Isolate* isolate = CcTest::i_isolate(); \ Isolate* isolate = CcTest::i_isolate(); \
HandleScope scope(isolate); \ HandleScope scope(isolate); \
CHECK_NOT_NULL(isolate); \ CHECK_NOT_NULL(isolate); \
std::unique_ptr<byte[]> owned_buf{new byte[buf_size]}; \ auto owned_buf = \
MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes, \ AllocateAssemblerBuffer(buf_size, nullptr, VirtualMemory::kNoJit); \
ExternalAssemblerBuffer(owned_buf.get(), buf_size)); \ MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes, \
Decoder<DispatchingDecoderVisitor>* decoder = \ ExternalAssemblerBuffer(owned_buf->start(), buf_size)); \
new Decoder<DispatchingDecoderVisitor>(); \ std::optional<AssemblerBufferWriteScope> rw_buffer_scope; \
Simulator simulator(decoder); \ Decoder<DispatchingDecoderVisitor>* decoder = \
std::unique_ptr<PrintDisassembler> pdis; \ new Decoder<DispatchingDecoderVisitor>(); \
RegisterDump core; \ Simulator simulator(decoder); \
HandleScope handle_scope(isolate); \ std::unique_ptr<PrintDisassembler> pdis; \
Handle<Code> code; \ RegisterDump core; \
if (i::FLAG_trace_sim) { \ HandleScope handle_scope(isolate); \
pdis.reset(new PrintDisassembler(stdout)); \ Handle<Code> code; \
decoder->PrependVisitor(pdis.get()); \ if (i::FLAG_trace_sim) { \
pdis.reset(new PrintDisassembler(stdout)); \
decoder->PrependVisitor(pdis.get()); \
} }
// Reset the assembler and simulator, so that instructions can be generated, // Reset the assembler and simulator, so that instructions can be generated,
...@@ -177,6 +179,7 @@ static void InitializeVM() { ...@@ -177,6 +179,7 @@ static void InitializeVM() {
HandleScope scope(isolate); \ HandleScope scope(isolate); \
CHECK_NOT_NULL(isolate); \ CHECK_NOT_NULL(isolate); \
auto owned_buf = AllocateAssemblerBuffer(buf_size); \ auto owned_buf = AllocateAssemblerBuffer(buf_size); \
std::optional<AssemblerBufferWriteScope> rw_buffer_scope; \
MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes, \ MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes, \
owned_buf->CreateView()); \ owned_buf->CreateView()); \
HandleScope handle_scope(isolate); \ HandleScope handle_scope(isolate); \
...@@ -184,7 +187,7 @@ static void InitializeVM() { ...@@ -184,7 +187,7 @@ static void InitializeVM() {
RegisterDump core; RegisterDump core;
#define RESET() \ #define RESET() \
owned_buf->MakeWritable(); \ rw_buffer_scope.emplace(*owned_buf); \
__ Reset(); \ __ Reset(); \
__ CodeEntry(); \ __ CodeEntry(); \
/* Reset the machine state (like simulator.ResetState()). */ \ /* Reset the machine state (like simulator.ResetState()). */ \
...@@ -198,10 +201,12 @@ static void InitializeVM() { ...@@ -198,10 +201,12 @@ static void InitializeVM() {
RESET(); \ RESET(); \
START_AFTER_RESET(); START_AFTER_RESET();
#define RUN() \ #define RUN() \
{ \ { \
auto f = GeneratedCode<void>::FromCode(*code); \ /* Reset the scope and thus make the buffer executable. */ \
f.Call(); \ rw_buffer_scope.reset(); \
auto f = GeneratedCode<void>::FromCode(*code); \
f.Call(); \
} }
#define END() \ #define END() \
...@@ -14880,6 +14885,7 @@ TEST(pool_size) { ...@@ -14880,6 +14885,7 @@ TEST(pool_size) {
// This test does not execute any code. It only tests that the size of the // This test does not execute any code. It only tests that the size of the
// pools is read correctly from the RelocInfo. // pools is read correctly from the RelocInfo.
rw_buffer_scope.emplace(*owned_buf);
Label exit; Label exit;
__ b(&exit); __ b(&exit);
......
...@@ -113,19 +113,18 @@ TEST(TestFlushICacheOfWritable) { ...@@ -113,19 +113,18 @@ TEST(TestFlushICacheOfWritable) {
// Allow calling the function from C++. // Allow calling the function from C++.
auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start()); auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start());
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(), {
buffer->size(), v8::PageAllocator::kReadWrite)); AssemblerBufferWriteScope rw_buffer_scope(*buffer);
FloodWithInc(isolate, buffer.get()); FloodWithInc(isolate, buffer.get());
FlushInstructionCache(buffer->start(), buffer->size()); 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_EQ(23 + kNumInstr, f.Call(23)); // Call into generated code.
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(),
buffer->size(), v8::PageAllocator::kReadWrite)); {
FloodWithNop(isolate, buffer.get()); AssemblerBufferWriteScope rw_buffer_scope(*buffer);
FlushInstructionCache(buffer->start(), buffer->size()); FloodWithNop(isolate, buffer.get());
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(), FlushInstructionCache(buffer->start(), buffer->size());
buffer->size(), v8::PageAllocator::kReadExecute)); }
CHECK_EQ(23, f.Call(23)); // Call into generated code. CHECK_EQ(23, f.Call(23)); // Call into generated code.
} }
} }
...@@ -184,24 +183,6 @@ TEST(TestFlushICacheOfWritableAndExecutable) { ...@@ -184,24 +183,6 @@ TEST(TestFlushICacheOfWritableAndExecutable) {
Isolate* isolate = CcTest::i_isolate(); Isolate* isolate = CcTest::i_isolate();
HandleScope handles(isolate); HandleScope handles(isolate);
struct V8_NODISCARD EnableWritePermissionsOnMacArm64Scope {
#if defined(V8_OS_DARWIN) && defined(V8_HOST_ARCH_ARM64)
// Ignoring this warning is considered better than relying on
// __builtin_available.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
EnableWritePermissionsOnMacArm64Scope() { pthread_jit_write_protect_np(0); }
~EnableWritePermissionsOnMacArm64Scope() {
pthread_jit_write_protect_np(1);
}
#pragma clang diagnostic pop
#else
EnableWritePermissionsOnMacArm64Scope() {
// Define a constructor to avoid unused variable warnings.
}
#endif
};
for (int i = 0; i < kNumIterations; ++i) { for (int i = 0; i < kNumIterations; ++i) {
auto buffer = AllocateAssemblerBuffer(kBufferSize, nullptr, auto buffer = AllocateAssemblerBuffer(kBufferSize, nullptr,
VirtualMemory::kMapAsJittable); VirtualMemory::kMapAsJittable);
...@@ -209,16 +190,16 @@ TEST(TestFlushICacheOfWritableAndExecutable) { ...@@ -209,16 +190,16 @@ TEST(TestFlushICacheOfWritableAndExecutable) {
// Allow calling the function from C++. // Allow calling the function from C++.
auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start()); auto f = GeneratedCode<F0>::FromBuffer(isolate, buffer->start());
CHECK(SetPermissions(GetPlatformPageAllocator(), buffer->start(), buffer->MakeWritableAndExecutable();
buffer->size(), v8::PageAllocator::kReadWriteExecute));
{ {
EnableWritePermissionsOnMacArm64Scope write_scope; RwxMemoryWriteScope rw_scope;
FloodWithInc(isolate, buffer.get()); FloodWithInc(isolate, buffer.get());
FlushInstructionCache(buffer->start(), buffer->size()); FlushInstructionCache(buffer->start(), buffer->size());
} }
CHECK_EQ(23 + kNumInstr, f.Call(23)); // Call into generated code. CHECK_EQ(23 + kNumInstr, f.Call(23)); // Call into generated code.
{ {
EnableWritePermissionsOnMacArm64Scope write_scope; RwxMemoryWriteScope rw_scope;
FloodWithNop(isolate, buffer.get()); FloodWithNop(isolate, buffer.get());
FlushInstructionCache(buffer->start(), buffer->size()); FlushInstructionCache(buffer->start(), buffer->size());
} }
......
...@@ -57,6 +57,8 @@ TEST(EmbeddedObj) { ...@@ -57,6 +57,8 @@ TEST(EmbeddedObj) {
MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes, MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes,
buffer->CreateView()); buffer->CreateView());
AssemblerBufferWriteScope rw_scope(*buffer);
Handle<HeapObject> old_array = isolate->factory()->NewFixedArray(2000); Handle<HeapObject> old_array = isolate->factory()->NewFixedArray(2000);
Handle<HeapObject> my_array = isolate->factory()->NewFixedArray(1000); Handle<HeapObject> my_array = isolate->factory()->NewFixedArray(1000);
__ Mov(w4, Immediate(my_array, RelocInfo::COMPRESSED_EMBEDDED_OBJECT)); __ Mov(w4, Immediate(my_array, RelocInfo::COMPRESSED_EMBEDDED_OBJECT));
...@@ -100,6 +102,8 @@ TEST(DeoptExitSizeIsFixed) { ...@@ -100,6 +102,8 @@ TEST(DeoptExitSizeIsFixed) {
MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes, MacroAssembler masm(isolate, v8::internal::CodeObjectRequired::kYes,
buffer->CreateView()); buffer->CreateView());
AssemblerBufferWriteScope rw_scope(*buffer);
STATIC_ASSERT(static_cast<int>(kFirstDeoptimizeKind) == 0); STATIC_ASSERT(static_cast<int>(kFirstDeoptimizeKind) == 0);
for (int i = 0; i < kDeoptimizeKindCount; i++) { for (int i = 0; i < kDeoptimizeKindCount; i++) {
DeoptimizeKind kind = static_cast<DeoptimizeKind>(i); DeoptimizeKind kind = static_cast<DeoptimizeKind>(i);
......
...@@ -55,17 +55,6 @@ constexpr uint32_t kAvailableBufferSlots = 0; ...@@ -55,17 +55,6 @@ constexpr uint32_t kAvailableBufferSlots = 0;
constexpr uint32_t kBufferSlotStartOffset = 0; constexpr uint32_t kBufferSlotStartOffset = 0;
#endif #endif
void EnsureThreadHasWritePermissions() {
#if defined(V8_OS_DARWIN) && defined(V8_HOST_ARCH_ARM64)
// Ignoring this warning is considered better than relying on
// __builtin_available.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
pthread_jit_write_protect_np(0);
#pragma clang diagnostic pop
#endif
}
Address AllocateJumpTableThunk( Address AllocateJumpTableThunk(
Address jump_target, byte* thunk_slot_buffer, Address jump_target, byte* thunk_slot_buffer,
std::bitset<kAvailableBufferSlots>* used_slots, std::bitset<kAvailableBufferSlots>* used_slots,
...@@ -214,7 +203,7 @@ class JumpTablePatcher : public v8::base::Thread { ...@@ -214,7 +203,7 @@ class JumpTablePatcher : public v8::base::Thread {
void Run() override { void Run() override {
TRACE("Patcher %p is starting ...\n", this); TRACE("Patcher %p is starting ...\n", this);
EnsureThreadHasWritePermissions(); RwxMemoryWriteScope rwx_write_scope;
Address slot_address = Address slot_address =
slot_start_ + JumpTableAssembler::JumpSlotIndexToOffset(slot_index_); slot_start_ + JumpTableAssembler::JumpSlotIndexToOffset(slot_index_);
// First, emit code to the two thunks. // First, emit code to the two thunks.
...@@ -265,11 +254,11 @@ TEST(JumpTablePatchingStress) { ...@@ -265,11 +254,11 @@ TEST(JumpTablePatchingStress) {
std::bitset<kAvailableBufferSlots> used_thunk_slots; std::bitset<kAvailableBufferSlots> used_thunk_slots;
buffer->MakeWritableAndExecutable(); buffer->MakeWritableAndExecutable();
RwxMemoryWriteScope rwx_write_scope;
// Iterate through jump-table slots to hammer at different alignments within // Iterate through jump-table slots to hammer at different alignments within
// the jump-table, thereby increasing stress for variable-length ISAs. // the jump-table, thereby increasing stress for variable-length ISAs.
Address slot_start = reinterpret_cast<Address>(buffer->start()); Address slot_start = reinterpret_cast<Address>(buffer->start());
EnsureThreadHasWritePermissions();
for (int slot = 0; slot < kJumpTableSlotCount; ++slot) { for (int slot = 0; slot < kJumpTableSlotCount; ++slot) {
TRACE("Hammering on jump table slot #%d ...\n", slot); TRACE("Hammering on jump table slot #%d ...\n", slot);
uint32_t slot_offset = JumpTableAssembler::JumpSlotIndexToOffset(slot); uint32_t slot_offset = JumpTableAssembler::JumpSlotIndexToOffset(slot);
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "src/codegen/assembler.h" #include "src/codegen/assembler.h"
#include "src/codegen/code-desc.h" #include "src/codegen/code-desc.h"
#include "src/common/code-memory-access-inl.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -27,7 +28,7 @@ class TestingAssemblerBuffer : public AssemblerBuffer { ...@@ -27,7 +28,7 @@ class TestingAssemblerBuffer : public AssemblerBuffer {
MakeWritable(); MakeWritable();
} }
~TestingAssemblerBuffer() { reservation_.Free(); } ~TestingAssemblerBuffer() override { reservation_.Free(); }
byte* start() const override { byte* start() const override {
return reinterpret_cast<byte*>(reservation_.address()); return reinterpret_cast<byte*>(reservation_.address());
...@@ -62,7 +63,6 @@ class TestingAssemblerBuffer : public AssemblerBuffer { ...@@ -62,7 +63,6 @@ class TestingAssemblerBuffer : public AssemblerBuffer {
CHECK(result); CHECK(result);
} }
// TODO(wasm): Only needed for the "test-jump-table-assembler.cc" tests.
void MakeWritableAndExecutable() { void MakeWritableAndExecutable() {
bool result = SetPermissions(GetPlatformPageAllocator(), start(), size(), bool result = SetPermissions(GetPlatformPageAllocator(), start(), size(),
v8::PageAllocator::kReadWriteExecute); v8::PageAllocator::kReadWriteExecute);
...@@ -73,6 +73,31 @@ class TestingAssemblerBuffer : public AssemblerBuffer { ...@@ -73,6 +73,31 @@ class TestingAssemblerBuffer : public AssemblerBuffer {
VirtualMemory reservation_; VirtualMemory reservation_;
}; };
// This scope class is mostly necesasry for arm64 tests running on Apple Silicon
// (M1) which prohibits reconfiguration of page permissions for RWX pages.
// Instead of altering the page permissions one must flip the X-W state by
// calling pthread_jit_write_protect_np() function.
// See RwxMemoryWriteScope for details.
class V8_NODISCARD AssemblerBufferWriteScope final {
public:
explicit AssemblerBufferWriteScope(TestingAssemblerBuffer& buffer)
: buffer_(buffer) {
buffer_.MakeWritable();
}
~AssemblerBufferWriteScope() { buffer_.MakeExecutable(); }
// Disable copy constructor and copy-assignment operator, since this manages
// a resource and implicit copying of the scope can yield surprising errors.
AssemblerBufferWriteScope(const AssemblerBufferWriteScope&) = delete;
AssemblerBufferWriteScope& operator=(const AssemblerBufferWriteScope&) =
delete;
private:
RwxMemoryWriteScope rwx_write_scope_;
TestingAssemblerBuffer& buffer_;
};
static inline std::unique_ptr<TestingAssemblerBuffer> AllocateAssemblerBuffer( static inline std::unique_ptr<TestingAssemblerBuffer> AllocateAssemblerBuffer(
size_t requested = v8::internal::AssemblerBase::kDefaultBufferSize, size_t requested = v8::internal::AssemblerBase::kDefaultBufferSize,
void* address = nullptr, void* address = nullptr,
......
...@@ -37,13 +37,16 @@ TEST_F(TurboAssemblerTest, TestHardAbort) { ...@@ -37,13 +37,16 @@ TEST_F(TurboAssemblerTest, TestHardAbort) {
__ set_root_array_available(false); __ set_root_array_available(false);
__ set_abort_hard(true); __ set_abort_hard(true);
__ CodeEntry(); {
AssemblerBufferWriteScope rw_scope(*buffer);
__ Abort(AbortReason::kNoReason); __ CodeEntry();
__ Abort(AbortReason::kNoReason);
CodeDesc desc; CodeDesc desc;
tasm.GetCode(isolate(), &desc); tasm.GetCode(isolate(), &desc);
buffer->MakeExecutable(); }
// We need an isolate here to execute in the simulator. // We need an isolate here to execute in the simulator.
auto f = GeneratedCode<void>::FromBuffer(isolate(), buffer->start()); auto f = GeneratedCode<void>::FromBuffer(isolate(), buffer->start());
...@@ -57,17 +60,20 @@ TEST_F(TurboAssemblerTest, TestCheck) { ...@@ -57,17 +60,20 @@ TEST_F(TurboAssemblerTest, TestCheck) {
__ set_root_array_available(false); __ set_root_array_available(false);
__ set_abort_hard(true); __ set_abort_hard(true);
__ CodeEntry(); {
AssemblerBufferWriteScope rw_scope(*buffer);
// Fail if the first parameter is 17. __ CodeEntry();
__ Mov(w1, Immediate(17));
__ Cmp(w0, w1); // 1st parameter is in {w0}.
__ Check(Condition::ne, AbortReason::kNoReason);
__ Ret();
CodeDesc desc; // Fail if the first parameter is 17.
tasm.GetCode(isolate(), &desc); __ Mov(w1, Immediate(17));
buffer->MakeExecutable(); __ Cmp(w0, w1); // 1st parameter is in {w0}.
__ Check(Condition::ne, AbortReason::kNoReason);
__ Ret();
CodeDesc desc;
tasm.GetCode(isolate(), &desc);
}
// We need an isolate here to execute in the simulator. // We need an isolate here to execute in the simulator.
auto f = GeneratedCode<void, int>::FromBuffer(isolate(), buffer->start()); auto f = GeneratedCode<void, int>::FromBuffer(isolate(), buffer->start());
...@@ -119,54 +125,58 @@ TEST_P(TurboAssemblerTestMoveObjectAndSlot, MoveObjectAndSlot) { ...@@ -119,54 +125,58 @@ TEST_P(TurboAssemblerTestMoveObjectAndSlot, MoveObjectAndSlot) {
TurboAssembler tasm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo, TurboAssembler tasm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
buffer->CreateView()); buffer->CreateView());
__ CodeEntry(); {
__ Push(x0, padreg); AssemblerBufferWriteScope rw_buffer_scope(*buffer);
__ Mov(test_case.object, x1);
__ CodeEntry();
Register src_object = test_case.object; __ Push(x0, padreg);
Register dst_object = test_case.dst_object; __ Mov(test_case.object, x1);
Register dst_slot = test_case.dst_slot;
Register src_object = test_case.object;
Operand offset_operand(0); Register dst_object = test_case.dst_object;
if (test_case.offset_register == no_reg) { Register dst_slot = test_case.dst_slot;
offset_operand = Operand(offset);
} else { Operand offset_operand(0);
__ Mov(test_case.offset_register, Operand(offset)); if (test_case.offset_register == no_reg) {
offset_operand = Operand(test_case.offset_register); offset_operand = Operand(offset);
} } else {
__ Mov(test_case.offset_register, Operand(offset));
std::stringstream comment; offset_operand = Operand(test_case.offset_register);
comment << "-- " << test_case.comment << ": MoveObjectAndSlot(" }
<< dst_object << ", " << dst_slot << ", " << src_object << ", ";
if (test_case.offset_register == no_reg) { std::stringstream comment;
comment << "#" << offset; comment << "-- " << test_case.comment << ": MoveObjectAndSlot("
} else { << dst_object << ", " << dst_slot << ", " << src_object << ", ";
comment << test_case.offset_register; if (test_case.offset_register == no_reg) {
} comment << "#" << offset;
comment << ") --"; } else {
__ RecordComment(comment.str().c_str()); comment << test_case.offset_register;
__ MoveObjectAndSlot(dst_object, dst_slot, src_object, offset_operand); }
__ RecordComment("--"); comment << ") --";
__ RecordComment(comment.str().c_str());
// The `result` pointer was saved on the stack. __ MoveObjectAndSlot(dst_object, dst_slot, src_object, offset_operand);
UseScratchRegisterScope temps(&tasm); __ RecordComment("--");
Register scratch = temps.AcquireX();
__ Pop(padreg, scratch); // The `result` pointer was saved on the stack.
__ Str(dst_object, MemOperand(scratch)); UseScratchRegisterScope temps(&tasm);
__ Str(dst_slot, MemOperand(scratch, kSystemPointerSize)); Register scratch = temps.AcquireX();
__ Pop(padreg, scratch);
__ Ret(); __ Str(dst_object, MemOperand(scratch));
__ Str(dst_slot, MemOperand(scratch, kSystemPointerSize));
CodeDesc desc;
tasm.GetCode(nullptr, &desc); __ Ret();
if (FLAG_print_code) {
Handle<Code> code = CodeDesc desc;
Factory::CodeBuilder(isolate(), desc, CodeKind::FOR_TESTING).Build(); tasm.GetCode(nullptr, &desc);
StdoutStream os; if (FLAG_print_code) {
code->Print(os); Handle<Code> code =
Factory::CodeBuilder(isolate(), desc, CodeKind::FOR_TESTING)
.Build();
StdoutStream os;
code->Print(os);
}
} }
buffer->MakeExecutable();
// We need an isolate here to execute in the simulator. // We need an isolate here to execute in the simulator.
auto f = GeneratedCode<void, byte**, byte*>::FromBuffer(isolate(), auto f = GeneratedCode<void, byte**, byte*>::FromBuffer(isolate(),
buffer->start()); buffer->start());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment