| // Copyright 2023 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/objects/instruction-stream.h" |
| |
| #include "src/builtins/builtins-inl.h" |
| #include "src/codegen/assembler-inl.h" |
| #include "src/codegen/flush-instruction-cache.h" |
| #include "src/codegen/reloc-info-inl.h" |
| #include "src/codegen/reloc-info.h" |
| #include "src/objects/instruction-stream-inl.h" |
| #include "src/objects/objects.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| void InstructionStream::Relocate(WritableJitAllocation& jit_allocation, |
| intptr_t delta) { |
| Tagged<Code> code; |
| if (!TryGetCodeUnchecked(&code, kAcquireLoad)) return; |
| // This is called during evacuation and code.instruction_stream() will point |
| // to the old object. So pass this directly to the RelocIterator. |
| for (WritableRelocIterator it(jit_allocation, this, constant_pool(), |
| RelocInfo::kApplyMask); |
| !it.done(); it.next()) { |
| it.rinfo()->apply(delta); |
| } |
| FlushInstructionCache(instruction_start(), body_size()); |
| } |
| |
| // This function performs the relocations but doesn't trigger any write barriers |
| // yet. We skip the write barriers here using SKIP_WRITE_BARRIER_SCOPE but the |
| // caller needs to call RelocateFromDescWriteBarriers afterwards. |
| InstructionStream::WriteBarrierPromise InstructionStream::RelocateFromDesc( |
| WritableJitAllocation& jit_allocation, Heap* heap, const CodeDesc& desc, |
| Address constant_pool, const DisallowGarbageCollection& no_gc) { |
| WriteBarrierPromise write_barrier_promise(this); |
| Assembler* origin = desc.origin; |
| const int mode_mask = RelocInfo::PostCodegenRelocationMask(); |
| for (WritableRelocIterator it(jit_allocation, this, constant_pool, mode_mask); |
| !it.done(); it.next()) { |
| // IMPORTANT: |
| // this code needs be stay in sync with RelocateFromDescWriteBarriers below. |
| |
| RelocInfo::Mode mode = it.rinfo()->rmode(); |
| if (RelocInfo::IsEmbeddedObjectMode(mode)) { |
| DirectHandle<HeapObject> p = it.rinfo()->target_object_handle(origin); |
| it.rinfo()->set_target_object(this, *p, SKIP_WRITE_BARRIER_SCOPE, |
| SKIP_ICACHE_FLUSH); |
| write_barrier_promise.RegisterAddress(it.rinfo()->pc()); |
| } else if (RelocInfo::IsCodeTargetMode(mode)) { |
| // Rewrite code handles to direct pointers to the first instruction in the |
| // code object. |
| DirectHandle<HeapObject> p = it.rinfo()->target_object_handle(origin); |
| Tagged<InstructionStream> target_istream = |
| TrustedCast<Code>(*p)->instruction_stream(); |
| it.rinfo()->set_target_address(this, target_istream->instruction_start(), |
| SKIP_WRITE_BARRIER_SCOPE, |
| SKIP_ICACHE_FLUSH); |
| write_barrier_promise.RegisterAddress(it.rinfo()->pc()); |
| } else if (RelocInfo::IsNearBuiltinEntry(mode)) { |
| // Rewrite builtin IDs to PC-relative offset to the builtin entry point. |
| Builtin builtin = it.rinfo()->target_builtin_at(origin); |
| Address p = Builtins::EntryOf(builtin, heap->isolate()); |
| // This won't trigger a write barrier, but setting mode to |
| // UPDATE_WRITE_BARRIER to make it clear that we didn't forget about it |
| // below. |
| it.rinfo()->set_target_address(this, p, UPDATE_WRITE_BARRIER, |
| SKIP_ICACHE_FLUSH); |
| DCHECK_EQ(p, it.rinfo()->target_address()); |
| } else if (RelocInfo::IsWasmStubCall(mode)) { |
| #if V8_ENABLE_WEBASSEMBLY |
| // Map wasm stub id to builtin. |
| uint32_t stub_call_tag = it.rinfo()->wasm_call_tag(); |
| DCHECK_LT(stub_call_tag, |
| static_cast<uint32_t>(Builtin::kFirstBytecodeHandler)); |
| Builtin builtin = static_cast<Builtin>(stub_call_tag); |
| // Store the builtin address in relocation info. |
| Address entry = Builtins::EntryOf(builtin, heap->isolate()); |
| it.rinfo()->set_wasm_stub_call_address(entry); |
| #else |
| UNREACHABLE(); |
| #endif |
| } else if (RelocInfo::IsJSDispatchHandle(mode)) { |
| write_barrier_promise.RegisterAddress(it.rinfo()->pc()); |
| } else { |
| intptr_t delta = |
| instruction_start() - reinterpret_cast<Address>(desc.buffer); |
| it.rinfo()->apply(delta); |
| } |
| } |
| return write_barrier_promise; |
| } |
| |
| void InstructionStream::RelocateFromDescWriteBarriers( |
| Heap* heap, const CodeDesc& desc, Address constant_pool, |
| WriteBarrierPromise& write_barrier_promise, |
| const DisallowGarbageCollection& no_gc) { |
| const int mode_mask = RelocInfo::PostCodegenRelocationMask(); |
| for (RelocIterator it(code(kAcquireLoad), mode_mask); !it.done(); it.next()) { |
| // IMPORTANT: |
| // this code needs be stay in sync with RelocateFromDesc above. |
| |
| RelocInfo::Mode mode = it.rinfo()->rmode(); |
| if (RelocInfo::IsEmbeddedObjectMode(mode)) { |
| Tagged<HeapObject> p = it.rinfo()->target_object(); |
| WriteBarrier::ForRelocInfo(this, it.rinfo(), p, UPDATE_WRITE_BARRIER); |
| write_barrier_promise.ResolveAddress(it.rinfo()->pc()); |
| } else if (RelocInfo::IsCodeTargetMode(mode)) { |
| Tagged<InstructionStream> target_istream = |
| InstructionStream::FromTargetAddress(it.rinfo()->target_address()); |
| WriteBarrier::ForRelocInfo(this, it.rinfo(), target_istream, |
| UPDATE_WRITE_BARRIER); |
| write_barrier_promise.ResolveAddress(it.rinfo()->pc()); |
| } else if (RelocInfo::IsJSDispatchHandle(mode)) { |
| JSDispatchHandle handle = it.rinfo()->js_dispatch_handle(); |
| WriteBarrier::ForJSDispatchHandle(this, handle, UPDATE_WRITE_BARRIER); |
| write_barrier_promise.ResolveAddress(it.rinfo()->pc()); |
| } |
| } |
| } |
| |
| #ifdef DEBUG |
| void InstructionStream::WriteBarrierPromise::RegisterAddress(Address address) { |
| DCHECK(delayed_write_barriers_.insert(address).second); |
| } |
| |
| void InstructionStream::WriteBarrierPromise::ResolveAddress(Address address) { |
| DCHECK_EQ(delayed_write_barriers_.erase(address), 1); |
| } |
| InstructionStream::WriteBarrierPromise::~WriteBarrierPromise() { |
| DCHECK(delayed_write_barriers_.empty()); |
| } |
| #endif |
| |
| void InstructionStream::ValidateJSDispatchHandles(Heap* heap, |
| const CodeDesc& desc) { |
| if (desc.origin) { |
| for (const auto& pair : desc.origin->js_dispatch_handles()) { |
| JSDispatchHandle handle = pair.first; |
| uint16_t expected_arg_count = pair.second; |
| uint16_t actual_arg_count = |
| heap->isolate()->js_dispatch_table().GetParameterCount(handle); |
| SBXCHECK_EQ(expected_arg_count, actual_arg_count); |
| } |
| } |
| } |
| |
| #ifdef V8_ENABLE_GENERATED_CODE_VALIDATOR |
| bool InstructionStream::IsValidated() const { |
| return ReadField<bool>(kIsValidatedOffset); |
| } |
| |
| void InstructionStream::SetValidated() { |
| DCHECK(!IsValidated()); |
| WritableJitAllocation writable_allocation = |
| ThreadIsolation::LookupJitAllocation( |
| address(), InstructionStream::SizeFor(body_size()), |
| ThreadIsolation::JitAllocationType::kInstructionStream); |
| DCHECK_EQ(InstructionStream::SizeFor(body_size()), |
| writable_allocation.size()); |
| writable_allocation.WriteHeaderSlot<bool, kIsValidatedOffset>(true); |
| } |
| #endif // V8_ENABLE_GENERATED_CODE_VALIDATOR |
| |
| } // namespace internal |
| } // namespace v8 |