| // Copyright 2012 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/builtins/builtins.h" |
| |
| #include "src/api-inl.h" |
| #include "src/assembler-inl.h" |
| #include "src/builtins/builtins-descriptors.h" |
| #include "src/callable.h" |
| #include "src/instruction-stream.h" |
| #include "src/isolate.h" |
| #include "src/macro-assembler.h" |
| #include "src/objects-inl.h" |
| #include "src/objects/fixed-array.h" |
| #include "src/visitors.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| // Forward declarations for C++ builtins. |
| #define FORWARD_DECLARE(Name) \ |
| Object* Builtin_##Name(int argc, Address* args, Isolate* isolate); |
| BUILTIN_LIST_C(FORWARD_DECLARE) |
| #undef FORWARD_DECLARE |
| |
| namespace { |
| |
| // TODO(jgruber): Pack in CallDescriptors::Key. |
| struct BuiltinMetadata { |
| const char* name; |
| Builtins::Kind kind; |
| union { |
| Address cpp_entry; // For CPP and API builtins. |
| int8_t parameter_count; // For TFJ builtins. |
| } kind_specific_data; |
| }; |
| |
| // clang-format off |
| #define DECL_CPP(Name, ...) { #Name, Builtins::CPP, \ |
| { FUNCTION_ADDR(Builtin_##Name) }}, |
| #define DECL_API(Name, ...) { #Name, Builtins::API, \ |
| { FUNCTION_ADDR(Builtin_##Name) }}, |
| #ifdef V8_TARGET_BIG_ENDIAN |
| #define DECL_TFJ(Name, Count, ...) { #Name, Builtins::TFJ, \ |
| { static_cast<Address>(static_cast<uintptr_t>( \ |
| Count) << (kBitsPerByte * (kPointerSize - 1))) }}, |
| #else |
| #define DECL_TFJ(Name, Count, ...) { #Name, Builtins::TFJ, \ |
| { static_cast<Address>(Count) }}, |
| #endif |
| #define DECL_TFC(Name, ...) { #Name, Builtins::TFC, {} }, |
| #define DECL_TFS(Name, ...) { #Name, Builtins::TFS, {} }, |
| #define DECL_TFH(Name, ...) { #Name, Builtins::TFH, {} }, |
| #define DECL_BCH(Name, ...) { #Name, Builtins::BCH, {} }, |
| #define DECL_ASM(Name, ...) { #Name, Builtins::ASM, {} }, |
| const BuiltinMetadata builtin_metadata[] = { |
| BUILTIN_LIST(DECL_CPP, DECL_API, DECL_TFJ, DECL_TFC, DECL_TFS, DECL_TFH, |
| DECL_BCH, DECL_ASM) |
| }; |
| #undef DECL_CPP |
| #undef DECL_API |
| #undef DECL_TFJ |
| #undef DECL_TFC |
| #undef DECL_TFS |
| #undef DECL_TFH |
| #undef DECL_BCH |
| #undef DECL_ASM |
| // clang-format on |
| |
| } // namespace |
| |
| BailoutId Builtins::GetContinuationBailoutId(Name name) { |
| DCHECK(Builtins::KindOf(name) == TFJ || Builtins::KindOf(name) == TFC); |
| return BailoutId(BailoutId::kFirstBuiltinContinuationId + name); |
| } |
| |
| Builtins::Name Builtins::GetBuiltinFromBailoutId(BailoutId id) { |
| int builtin_index = id.ToInt() - BailoutId::kFirstBuiltinContinuationId; |
| DCHECK(Builtins::KindOf(builtin_index) == TFJ || |
| Builtins::KindOf(builtin_index) == TFC); |
| return static_cast<Name>(builtin_index); |
| } |
| |
| void Builtins::TearDown() { initialized_ = false; } |
| |
| const char* Builtins::Lookup(Address pc) { |
| // Off-heap pc's can be looked up through binary search. |
| if (FLAG_embedded_builtins) { |
| Code* maybe_builtin = InstructionStream::TryLookupCode(isolate_, pc); |
| if (maybe_builtin != nullptr) return name(maybe_builtin->builtin_index()); |
| } |
| |
| // May be called during initialization (disassembler). |
| if (initialized_) { |
| for (int i = 0; i < builtin_count; i++) { |
| if (isolate_->heap()->builtin(i)->contains(pc)) return name(i); |
| } |
| } |
| return nullptr; |
| } |
| |
| Handle<Code> Builtins::NewFunctionContext(ScopeType scope_type) { |
| switch (scope_type) { |
| case ScopeType::EVAL_SCOPE: |
| return builtin_handle(kFastNewFunctionContextEval); |
| case ScopeType::FUNCTION_SCOPE: |
| return builtin_handle(kFastNewFunctionContextFunction); |
| default: |
| UNREACHABLE(); |
| } |
| return Handle<Code>::null(); |
| } |
| |
| Handle<Code> Builtins::NonPrimitiveToPrimitive(ToPrimitiveHint hint) { |
| switch (hint) { |
| case ToPrimitiveHint::kDefault: |
| return builtin_handle(kNonPrimitiveToPrimitive_Default); |
| case ToPrimitiveHint::kNumber: |
| return builtin_handle(kNonPrimitiveToPrimitive_Number); |
| case ToPrimitiveHint::kString: |
| return builtin_handle(kNonPrimitiveToPrimitive_String); |
| } |
| UNREACHABLE(); |
| } |
| |
| Handle<Code> Builtins::OrdinaryToPrimitive(OrdinaryToPrimitiveHint hint) { |
| switch (hint) { |
| case OrdinaryToPrimitiveHint::kNumber: |
| return builtin_handle(kOrdinaryToPrimitive_Number); |
| case OrdinaryToPrimitiveHint::kString: |
| return builtin_handle(kOrdinaryToPrimitive_String); |
| } |
| UNREACHABLE(); |
| } |
| |
| void Builtins::set_builtin(int index, HeapObject* builtin) { |
| isolate_->heap()->set_builtin(index, builtin); |
| } |
| |
| Code* Builtins::builtin(int index) { return isolate_->heap()->builtin(index); } |
| |
| Handle<Code> Builtins::builtin_handle(int index) { |
| DCHECK(IsBuiltinId(index)); |
| return Handle<Code>( |
| reinterpret_cast<Code**>(isolate_->heap()->builtin_address(index))); |
| } |
| |
| // static |
| int Builtins::GetStackParameterCount(Name name) { |
| DCHECK(Builtins::KindOf(name) == TFJ); |
| return builtin_metadata[name].kind_specific_data.parameter_count; |
| } |
| |
| // static |
| Callable Builtins::CallableFor(Isolate* isolate, Name name) { |
| Handle<Code> code = isolate->builtins()->builtin_handle(name); |
| CallDescriptors::Key key; |
| switch (name) { |
| // This macro is deliberately crafted so as to emit very little code, |
| // in order to keep binary size of this function under control. |
| #define CASE_OTHER(Name, ...) \ |
| case k##Name: { \ |
| key = Builtin_##Name##_InterfaceDescriptor::key(); \ |
| break; \ |
| } |
| BUILTIN_LIST(IGNORE_BUILTIN, IGNORE_BUILTIN, IGNORE_BUILTIN, CASE_OTHER, |
| CASE_OTHER, CASE_OTHER, IGNORE_BUILTIN, IGNORE_BUILTIN) |
| #undef CASE_OTHER |
| default: |
| Builtins::Kind kind = Builtins::KindOf(name); |
| DCHECK(kind != BCH && kind != DLH); |
| if (kind == TFJ || kind == CPP) { |
| return Callable(code, JSTrampolineDescriptor{}); |
| } |
| UNREACHABLE(); |
| } |
| CallInterfaceDescriptor descriptor(key); |
| return Callable(code, descriptor); |
| } |
| |
| // static |
| const char* Builtins::name(int index) { |
| DCHECK(IsBuiltinId(index)); |
| return builtin_metadata[index].name; |
| } |
| |
| // static |
| Address Builtins::CppEntryOf(int index) { |
| DCHECK(Builtins::HasCppImplementation(index)); |
| return builtin_metadata[index].kind_specific_data.cpp_entry; |
| } |
| |
| // static |
| bool Builtins::IsBuiltin(const Code* code) { |
| return Builtins::IsBuiltinId(code->builtin_index()); |
| } |
| |
| bool Builtins::IsBuiltinHandle(Handle<HeapObject> maybe_code, |
| int* index) const { |
| Heap* heap = isolate_->heap(); |
| Address handle_location = maybe_code.address(); |
| Address start = heap->builtin_address(0); |
| Address end = heap->builtin_address(Builtins::builtin_count); |
| if (handle_location >= end) return false; |
| if (handle_location < start) return false; |
| *index = static_cast<int>(handle_location - start) >> kPointerSizeLog2; |
| DCHECK(Builtins::IsBuiltinId(*index)); |
| return true; |
| } |
| |
| // static |
| bool Builtins::IsIsolateIndependentBuiltin(const Code* code) { |
| if (FLAG_embedded_builtins) { |
| const int builtin_index = code->builtin_index(); |
| return Builtins::IsBuiltinId(builtin_index) && |
| Builtins::IsIsolateIndependent(builtin_index); |
| } else { |
| return false; |
| } |
| } |
| |
| // static |
| bool Builtins::IsIsolateIndependent(int index) { |
| DCHECK(IsBuiltinId(index)); |
| switch (index) { |
| // TODO(jgruber): There's currently two blockers for moving |
| // InterpreterEntryTrampoline into the binary: |
| // 1. InterpreterEnterBytecode calculates a pointer into the middle of |
| // InterpreterEntryTrampoline (see interpreter_entry_return_pc_offset). |
| // When the builtin is embedded, the pointer would need to be calculated |
| // at an offset from the embedded instruction stream (instead of the |
| // trampoline code object). |
| // 2. We create distinct copies of the trampoline to make it possible to |
| // attribute ticks in the interpreter to individual JS functions. |
| // See https://crrev.com/c/959081 and InstallBytecodeArray. When the |
| // trampoline is embedded, we need to ensure that CopyCode creates a copy |
| // of the builtin itself (and not just the trampoline). |
| case kInterpreterEntryTrampoline: |
| return false; |
| default: |
| return true; |
| } |
| UNREACHABLE(); |
| } |
| |
| // static |
| bool Builtins::IsWasmRuntimeStub(int index) { |
| DCHECK(IsBuiltinId(index)); |
| switch (index) { |
| #define CASE_TRAP(Name) case kThrowWasm##Name: |
| #define CASE(Name) case k##Name: |
| WASM_RUNTIME_STUB_LIST(CASE, CASE_TRAP) |
| #undef CASE_TRAP |
| #undef CASE |
| return true; |
| default: |
| return false; |
| } |
| UNREACHABLE(); |
| } |
| |
| namespace { |
| |
| class OffHeapTrampolineGenerator { |
| public: |
| explicit OffHeapTrampolineGenerator(Isolate* isolate) |
| : isolate_(isolate), |
| masm_(isolate, buffer, kBufferSize, CodeObjectRequired::kYes) {} |
| |
| CodeDesc Generate(Address off_heap_entry) { |
| // Generate replacement code that simply tail-calls the off-heap code. |
| DCHECK(!masm_.has_frame()); |
| { |
| FrameScope scope(&masm_, StackFrame::NONE); |
| masm_.JumpToInstructionStream(off_heap_entry); |
| } |
| |
| CodeDesc desc; |
| masm_.GetCode(isolate_, &desc); |
| return desc; |
| } |
| |
| Handle<HeapObject> CodeObject() { return masm_.CodeObject(); } |
| |
| private: |
| Isolate* isolate_; |
| // Enough to fit the single jmp. |
| static constexpr size_t kBufferSize = 256; |
| byte buffer[kBufferSize]; |
| MacroAssembler masm_; |
| }; |
| |
| } // namespace |
| |
| // static |
| Handle<Code> Builtins::GenerateOffHeapTrampolineFor(Isolate* isolate, |
| Address off_heap_entry) { |
| DCHECK(isolate->serializer_enabled()); |
| DCHECK_NOT_NULL(isolate->embedded_blob()); |
| DCHECK_NE(0, isolate->embedded_blob_size()); |
| |
| OffHeapTrampolineGenerator generator(isolate); |
| CodeDesc desc = generator.Generate(off_heap_entry); |
| |
| return isolate->factory()->NewCode(desc, Code::BUILTIN, |
| generator.CodeObject()); |
| } |
| |
| // static |
| Handle<ByteArray> Builtins::GenerateOffHeapTrampolineRelocInfo( |
| Isolate* isolate) { |
| OffHeapTrampolineGenerator generator(isolate); |
| // Generate a jump to a dummy address as we're not actually interested in the |
| // generated instruction stream. |
| CodeDesc desc = generator.Generate(kNullAddress); |
| |
| Handle<ByteArray> reloc_info = |
| isolate->factory()->NewByteArray(desc.reloc_size, TENURED_READ_ONLY); |
| Code::CopyRelocInfoToByteArray(*reloc_info, desc); |
| |
| return reloc_info; |
| } |
| |
| // static |
| Builtins::Kind Builtins::KindOf(int index) { |
| DCHECK(IsBuiltinId(index)); |
| return builtin_metadata[index].kind; |
| } |
| |
| // static |
| const char* Builtins::KindNameOf(int index) { |
| Kind kind = Builtins::KindOf(index); |
| // clang-format off |
| switch (kind) { |
| case CPP: return "CPP"; |
| case API: return "API"; |
| case TFJ: return "TFJ"; |
| case TFC: return "TFC"; |
| case TFS: return "TFS"; |
| case TFH: return "TFH"; |
| case BCH: return "BCH"; |
| case DLH: return "DLH"; |
| case ASM: return "ASM"; |
| } |
| // clang-format on |
| UNREACHABLE(); |
| } |
| |
| // static |
| bool Builtins::IsCpp(int index) { return Builtins::KindOf(index) == CPP; } |
| |
| // static |
| bool Builtins::HasCppImplementation(int index) { |
| Kind kind = Builtins::KindOf(index); |
| return (kind == CPP || kind == API); |
| } |
| |
| // static |
| bool Builtins::AllowDynamicFunction(Isolate* isolate, Handle<JSFunction> target, |
| Handle<JSObject> target_global_proxy) { |
| if (FLAG_allow_unsafe_function_constructor) return true; |
| HandleScopeImplementer* impl = isolate->handle_scope_implementer(); |
| Handle<Context> responsible_context = |
| impl->MicrotaskContextIsLastEnteredContext() ? impl->MicrotaskContext() |
| : impl->LastEnteredContext(); |
| // TODO(jochen): Remove this. |
| if (responsible_context.is_null()) { |
| return true; |
| } |
| if (*responsible_context == target->context()) return true; |
| return isolate->MayAccess(responsible_context, target_global_proxy); |
| } |
| |
| Builtins::Name ExampleBuiltinForTorqueFunctionPointerType( |
| size_t function_pointer_type_id) { |
| switch (function_pointer_type_id) { |
| #define FUNCTION_POINTER_ID_CASE(id, name) \ |
| case id: \ |
| return Builtins::k##name; |
| TORQUE_FUNCTION_POINTER_TYPE_TO_BUILTIN_MAP(FUNCTION_POINTER_ID_CASE) |
| #undef FUNCTION_POINTER_ID_CASE |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |