• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/builtins/builtins.h"
6 
7 #include "src/api/api-inl.h"
8 #include "src/builtins/builtins-descriptors.h"
9 #include "src/codegen/assembler-inl.h"
10 #include "src/codegen/callable.h"
11 #include "src/codegen/macro-assembler-inl.h"
12 #include "src/codegen/macro-assembler.h"
13 #include "src/diagnostics/code-tracer.h"
14 #include "src/execution/isolate.h"
15 #include "src/interpreter/bytecodes.h"
16 #include "src/logging/code-events.h"  // For CodeCreateEvent.
17 #include "src/logging/log.h"          // For Logger.
18 #include "src/objects/fixed-array.h"
19 #include "src/objects/objects-inl.h"
20 #include "src/objects/visitors.h"
21 #include "src/snapshot/embedded/embedded-data.h"
22 #include "src/utils/ostreams.h"
23 
24 namespace v8 {
25 namespace internal {
26 
27 // Forward declarations for C++ builtins.
28 #define FORWARD_DECLARE(Name) \
29   Address Builtin_##Name(int argc, Address* args, Isolate* isolate);
30 BUILTIN_LIST_C(FORWARD_DECLARE)
31 #undef FORWARD_DECLARE
32 
33 namespace {
34 
35 // TODO(jgruber): Pack in CallDescriptors::Key.
36 struct BuiltinMetadata {
37   const char* name;
38   Builtins::Kind kind;
39 
40   struct BytecodeAndScale {
41     interpreter::Bytecode bytecode : 8;
42     interpreter::OperandScale scale : 8;
43   };
44 
45   STATIC_ASSERT(sizeof(interpreter::Bytecode) == 1);
46   STATIC_ASSERT(sizeof(interpreter::OperandScale) == 1);
47   STATIC_ASSERT(sizeof(BytecodeAndScale) <= sizeof(Address));
48 
49   // The `data` field has kind-specific contents.
50   union KindSpecificData {
51     // TODO(jgruber): Union constructors are needed since C++11 does not support
52     // designated initializers (e.g.: {.parameter_count = count}). Update once
53     // we're at C++20 :)
54     // The constructors are marked constexpr to avoid the need for a static
55     // initializer for builtins.cc (see check-static-initializers.sh).
KindSpecificData()56     constexpr KindSpecificData() : cpp_entry(kNullAddress) {}
KindSpecificData(Address cpp_entry)57     constexpr KindSpecificData(Address cpp_entry) : cpp_entry(cpp_entry) {}
KindSpecificData(int parameter_count,int)58     constexpr KindSpecificData(int parameter_count,
59                                int /* To disambiguate from above */)
60         : parameter_count(static_cast<int16_t>(parameter_count)) {}
KindSpecificData(interpreter::Bytecode bytecode,interpreter::OperandScale scale)61     constexpr KindSpecificData(interpreter::Bytecode bytecode,
62                                interpreter::OperandScale scale)
63         : bytecode_and_scale{bytecode, scale} {}
64     Address cpp_entry;                    // For CPP builtins.
65     int16_t parameter_count;              // For TFJ builtins.
66     BytecodeAndScale bytecode_and_scale;  // For BCH builtins.
67   } data;
68 };
69 
70 #define DECL_CPP(Name, ...) \
71   {#Name, Builtins::CPP, {FUNCTION_ADDR(Builtin_##Name)}},
72 #define DECL_TFJ(Name, Count, ...) {#Name, Builtins::TFJ, {Count, 0}},
73 #define DECL_TFC(Name, ...) {#Name, Builtins::TFC, {}},
74 #define DECL_TFS(Name, ...) {#Name, Builtins::TFS, {}},
75 #define DECL_TFH(Name, ...) {#Name, Builtins::TFH, {}},
76 #define DECL_BCH(Name, OperandScale, Bytecode) \
77   {#Name, Builtins::BCH, {Bytecode, OperandScale}},
78 #define DECL_ASM(Name, ...) {#Name, Builtins::ASM, {}},
79 const BuiltinMetadata builtin_metadata[] = {BUILTIN_LIST(
80     DECL_CPP, DECL_TFJ, DECL_TFC, DECL_TFS, DECL_TFH, DECL_BCH, DECL_ASM)};
81 #undef DECL_CPP
82 #undef DECL_TFJ
83 #undef DECL_TFC
84 #undef DECL_TFS
85 #undef DECL_TFH
86 #undef DECL_BCH
87 #undef DECL_ASM
88 
89 }  // namespace
90 
GetContinuationBailoutId(Name name)91 BailoutId Builtins::GetContinuationBailoutId(Name name) {
92   DCHECK(Builtins::KindOf(name) == TFJ || Builtins::KindOf(name) == TFC ||
93          Builtins::KindOf(name) == TFS);
94   return BailoutId(BailoutId::kFirstBuiltinContinuationId + name);
95 }
96 
GetBuiltinFromBailoutId(BailoutId id)97 Builtins::Name Builtins::GetBuiltinFromBailoutId(BailoutId id) {
98   int builtin_index = id.ToInt() - BailoutId::kFirstBuiltinContinuationId;
99   DCHECK(Builtins::KindOf(builtin_index) == TFJ ||
100          Builtins::KindOf(builtin_index) == TFC ||
101          Builtins::KindOf(builtin_index) == TFS);
102   return static_cast<Name>(builtin_index);
103 }
104 
TearDown()105 void Builtins::TearDown() { initialized_ = false; }
106 
Lookup(Address pc)107 const char* Builtins::Lookup(Address pc) {
108   // Off-heap pc's can be looked up through binary search.
109   Code maybe_builtin = InstructionStream::TryLookupCode(isolate_, pc);
110   if (!maybe_builtin.is_null()) return name(maybe_builtin.builtin_index());
111 
112   // May be called during initialization (disassembler).
113   if (initialized_) {
114     for (int i = 0; i < builtin_count; i++) {
115       if (isolate_->heap()->builtin(i).contains(pc)) return name(i);
116     }
117   }
118   return nullptr;
119 }
120 
CallFunction(ConvertReceiverMode mode)121 Handle<Code> Builtins::CallFunction(ConvertReceiverMode mode) {
122   switch (mode) {
123     case ConvertReceiverMode::kNullOrUndefined:
124       return builtin_handle(kCallFunction_ReceiverIsNullOrUndefined);
125     case ConvertReceiverMode::kNotNullOrUndefined:
126       return builtin_handle(kCallFunction_ReceiverIsNotNullOrUndefined);
127     case ConvertReceiverMode::kAny:
128       return builtin_handle(kCallFunction_ReceiverIsAny);
129   }
130   UNREACHABLE();
131 }
132 
Call(ConvertReceiverMode mode)133 Handle<Code> Builtins::Call(ConvertReceiverMode mode) {
134   switch (mode) {
135     case ConvertReceiverMode::kNullOrUndefined:
136       return builtin_handle(kCall_ReceiverIsNullOrUndefined);
137     case ConvertReceiverMode::kNotNullOrUndefined:
138       return builtin_handle(kCall_ReceiverIsNotNullOrUndefined);
139     case ConvertReceiverMode::kAny:
140       return builtin_handle(kCall_ReceiverIsAny);
141   }
142   UNREACHABLE();
143 }
144 
NonPrimitiveToPrimitive(ToPrimitiveHint hint)145 Handle<Code> Builtins::NonPrimitiveToPrimitive(ToPrimitiveHint hint) {
146   switch (hint) {
147     case ToPrimitiveHint::kDefault:
148       return builtin_handle(kNonPrimitiveToPrimitive_Default);
149     case ToPrimitiveHint::kNumber:
150       return builtin_handle(kNonPrimitiveToPrimitive_Number);
151     case ToPrimitiveHint::kString:
152       return builtin_handle(kNonPrimitiveToPrimitive_String);
153   }
154   UNREACHABLE();
155 }
156 
OrdinaryToPrimitive(OrdinaryToPrimitiveHint hint)157 Handle<Code> Builtins::OrdinaryToPrimitive(OrdinaryToPrimitiveHint hint) {
158   switch (hint) {
159     case OrdinaryToPrimitiveHint::kNumber:
160       return builtin_handle(kOrdinaryToPrimitive_Number);
161     case OrdinaryToPrimitiveHint::kString:
162       return builtin_handle(kOrdinaryToPrimitive_String);
163   }
164   UNREACHABLE();
165 }
166 
set_builtin(int index,Code builtin)167 void Builtins::set_builtin(int index, Code builtin) {
168   isolate_->heap()->set_builtin(index, builtin);
169 }
170 
builtin(int index)171 Code Builtins::builtin(int index) { return isolate_->heap()->builtin(index); }
172 
builtin_handle(int index)173 Handle<Code> Builtins::builtin_handle(int index) {
174   DCHECK(IsBuiltinId(index));
175   return Handle<Code>(
176       reinterpret_cast<Address*>(isolate_->heap()->builtin_address(index)));
177 }
178 
179 // static
GetStackParameterCount(Name name)180 int Builtins::GetStackParameterCount(Name name) {
181   DCHECK(Builtins::KindOf(name) == TFJ);
182   return builtin_metadata[name].data.parameter_count;
183 }
184 
185 // static
CallInterfaceDescriptorFor(Name name)186 CallInterfaceDescriptor Builtins::CallInterfaceDescriptorFor(Name name) {
187   CallDescriptors::Key key;
188   switch (name) {
189 // This macro is deliberately crafted so as to emit very little code,
190 // in order to keep binary size of this function under control.
191 #define CASE_OTHER(Name, ...)                          \
192   case k##Name: {                                      \
193     key = Builtin_##Name##_InterfaceDescriptor::key(); \
194     break;                                             \
195   }
196     BUILTIN_LIST(IGNORE_BUILTIN, IGNORE_BUILTIN, CASE_OTHER, CASE_OTHER,
197                  CASE_OTHER, IGNORE_BUILTIN, CASE_OTHER)
198 #undef CASE_OTHER
199     default:
200       Builtins::Kind kind = Builtins::KindOf(name);
201       DCHECK_NE(BCH, kind);
202       if (kind == TFJ || kind == CPP) {
203         return JSTrampolineDescriptor{};
204       }
205       UNREACHABLE();
206   }
207   return CallInterfaceDescriptor{key};
208 }
209 
210 // static
CallableFor(Isolate * isolate,Name name)211 Callable Builtins::CallableFor(Isolate* isolate, Name name) {
212   Handle<Code> code = isolate->builtins()->builtin_handle(name);
213   return Callable{code, CallInterfaceDescriptorFor(name)};
214 }
215 
216 // static
HasJSLinkage(int builtin_index)217 bool Builtins::HasJSLinkage(int builtin_index) {
218   Name name = static_cast<Name>(builtin_index);
219   DCHECK_NE(BCH, Builtins::KindOf(name));
220   return CallInterfaceDescriptorFor(name) == JSTrampolineDescriptor{};
221 }
222 
223 // static
name(int index)224 const char* Builtins::name(int index) {
225   DCHECK(IsBuiltinId(index));
226   return builtin_metadata[index].name;
227 }
228 
PrintBuiltinCode()229 void Builtins::PrintBuiltinCode() {
230   DCHECK(FLAG_print_builtin_code);
231 #ifdef ENABLE_DISASSEMBLER
232   for (int i = 0; i < builtin_count; i++) {
233     const char* builtin_name = name(i);
234     Handle<Code> code = builtin_handle(i);
235     if (PassesFilter(CStrVector(builtin_name),
236                      CStrVector(FLAG_print_builtin_code_filter))) {
237       CodeTracer::Scope trace_scope(isolate_->GetCodeTracer());
238       OFStream os(trace_scope.file());
239       code->Disassemble(builtin_name, os, isolate_);
240       os << "\n";
241     }
242   }
243 #endif
244 }
245 
PrintBuiltinSize()246 void Builtins::PrintBuiltinSize() {
247   DCHECK(FLAG_print_builtin_size);
248   for (int i = 0; i < builtin_count; i++) {
249     const char* builtin_name = name(i);
250     const char* kind = KindNameOf(i);
251     Code code = builtin(i);
252     PrintF(stdout, "%s Builtin, %s, %d\n", kind, builtin_name,
253            code.InstructionSize());
254   }
255 }
256 
257 // static
CppEntryOf(int index)258 Address Builtins::CppEntryOf(int index) {
259   DCHECK(Builtins::IsCpp(index));
260   return builtin_metadata[index].data.cpp_entry;
261 }
262 
263 // static
IsBuiltin(const Code code)264 bool Builtins::IsBuiltin(const Code code) {
265   return Builtins::IsBuiltinId(code.builtin_index());
266 }
267 
IsBuiltinHandle(Handle<HeapObject> maybe_code,int * index) const268 bool Builtins::IsBuiltinHandle(Handle<HeapObject> maybe_code,
269                                int* index) const {
270   Heap* heap = isolate_->heap();
271   Address handle_location = maybe_code.address();
272   Address start = heap->builtin_address(0);
273   Address end = heap->builtin_address(Builtins::builtin_count);
274   if (handle_location >= end) return false;
275   if (handle_location < start) return false;
276   *index = static_cast<int>(handle_location - start) >> kSystemPointerSizeLog2;
277   DCHECK(Builtins::IsBuiltinId(*index));
278   return true;
279 }
280 
281 // static
IsIsolateIndependentBuiltin(const Code code)282 bool Builtins::IsIsolateIndependentBuiltin(const Code code) {
283   const int builtin_index = code.builtin_index();
284   return Builtins::IsBuiltinId(builtin_index) &&
285          Builtins::IsIsolateIndependent(builtin_index);
286 }
287 
288 // static
InitializeBuiltinEntryTable(Isolate * isolate)289 void Builtins::InitializeBuiltinEntryTable(Isolate* isolate) {
290   EmbeddedData d = EmbeddedData::FromBlob();
291   Address* builtin_entry_table = isolate->builtin_entry_table();
292   for (int i = 0; i < builtin_count; i++) {
293     // TODO(jgruber,chromium:1020986): Remove the CHECK once the linked issue is
294     // resolved.
295     CHECK(Builtins::IsBuiltinId(isolate->heap()->builtin(i).builtin_index()));
296     DCHECK(isolate->heap()->builtin(i).is_off_heap_trampoline());
297     builtin_entry_table[i] = d.InstructionStartOfBuiltin(i);
298   }
299 }
300 
301 // static
EmitCodeCreateEvents(Isolate * isolate)302 void Builtins::EmitCodeCreateEvents(Isolate* isolate) {
303   if (!isolate->logger()->is_listening_to_code_events() &&
304       !isolate->is_profiling()) {
305     return;  // No need to iterate the entire table in this case.
306   }
307 
308   Address* builtins = isolate->builtins_table();
309   int i = 0;
310   HandleScope scope(isolate);
311   for (; i < kFirstBytecodeHandler; i++) {
312     Handle<AbstractCode> code(AbstractCode::cast(Object(builtins[i])), isolate);
313     PROFILE(isolate, CodeCreateEvent(CodeEventListener::BUILTIN_TAG, code,
314                                      Builtins::name(i)));
315   }
316 
317   STATIC_ASSERT(kLastBytecodeHandlerPlusOne == builtin_count);
318   for (; i < builtin_count; i++) {
319     Handle<AbstractCode> code(AbstractCode::cast(Object(builtins[i])), isolate);
320     interpreter::Bytecode bytecode =
321         builtin_metadata[i].data.bytecode_and_scale.bytecode;
322     interpreter::OperandScale scale =
323         builtin_metadata[i].data.bytecode_and_scale.scale;
324     PROFILE(isolate,
325             CodeCreateEvent(
326                 CodeEventListener::BYTECODE_HANDLER_TAG, code,
327                 interpreter::Bytecodes::ToString(bytecode, scale).c_str()));
328   }
329 }
330 
331 namespace {
332 enum TrampolineType { kAbort, kJump };
333 
334 class OffHeapTrampolineGenerator {
335  public:
OffHeapTrampolineGenerator(Isolate * isolate)336   explicit OffHeapTrampolineGenerator(Isolate* isolate)
337       : isolate_(isolate),
338         masm_(isolate, AssemblerOptions::DefaultForOffHeapTrampoline(isolate),
339               CodeObjectRequired::kYes,
340               ExternalAssemblerBuffer(buffer_, kBufferSize)) {}
341 
Generate(Address off_heap_entry,TrampolineType type)342   CodeDesc Generate(Address off_heap_entry, TrampolineType type) {
343     // Generate replacement code that simply tail-calls the off-heap code.
344     DCHECK(!masm_.has_frame());
345     {
346       FrameScope scope(&masm_, StackFrame::NONE);
347       if (type == TrampolineType::kJump) {
348         masm_.CodeEntry();
349         masm_.JumpToInstructionStream(off_heap_entry);
350       } else {
351         DCHECK_EQ(type, TrampolineType::kAbort);
352         masm_.Trap();
353       }
354     }
355 
356     CodeDesc desc;
357     masm_.GetCode(isolate_, &desc);
358     return desc;
359   }
360 
CodeObject()361   Handle<HeapObject> CodeObject() { return masm_.CodeObject(); }
362 
363  private:
364   Isolate* isolate_;
365   // Enough to fit the single jmp.
366   static constexpr int kBufferSize = 256;
367   byte buffer_[kBufferSize];
368   MacroAssembler masm_;
369 };
370 
371 constexpr int OffHeapTrampolineGenerator::kBufferSize;
372 
373 }  // namespace
374 
375 // static
GenerateOffHeapTrampolineFor(Isolate * isolate,Address off_heap_entry,int32_t kind_specfic_flags,bool generate_jump_to_instruction_stream)376 Handle<Code> Builtins::GenerateOffHeapTrampolineFor(
377     Isolate* isolate, Address off_heap_entry, int32_t kind_specfic_flags,
378     bool generate_jump_to_instruction_stream) {
379   DCHECK_NOT_NULL(isolate->embedded_blob_code());
380   DCHECK_NE(0, isolate->embedded_blob_code_size());
381 
382   OffHeapTrampolineGenerator generator(isolate);
383 
384   CodeDesc desc =
385       generator.Generate(off_heap_entry, generate_jump_to_instruction_stream
386                                              ? TrampolineType::kJump
387                                              : TrampolineType::kAbort);
388 
389   return Factory::CodeBuilder(isolate, desc, CodeKind::BUILTIN)
390       .set_read_only_data_container(kind_specfic_flags)
391       .set_self_reference(generator.CodeObject())
392       .set_is_executable(generate_jump_to_instruction_stream)
393       .Build();
394 }
395 
396 // static
GenerateOffHeapTrampolineRelocInfo(Isolate * isolate)397 Handle<ByteArray> Builtins::GenerateOffHeapTrampolineRelocInfo(
398     Isolate* isolate) {
399   OffHeapTrampolineGenerator generator(isolate);
400   // Generate a jump to a dummy address as we're not actually interested in the
401   // generated instruction stream.
402   CodeDesc desc = generator.Generate(kNullAddress, TrampolineType::kJump);
403 
404   Handle<ByteArray> reloc_info = isolate->factory()->NewByteArray(
405       desc.reloc_size, AllocationType::kReadOnly);
406   Code::CopyRelocInfoToByteArray(*reloc_info, desc);
407 
408   return reloc_info;
409 }
410 
411 // static
KindOf(int index)412 Builtins::Kind Builtins::KindOf(int index) {
413   DCHECK(IsBuiltinId(index));
414   return builtin_metadata[index].kind;
415 }
416 
417 // static
KindNameOf(int index)418 const char* Builtins::KindNameOf(int index) {
419   Kind kind = Builtins::KindOf(index);
420   // clang-format off
421   switch (kind) {
422     case CPP: return "CPP";
423     case TFJ: return "TFJ";
424     case TFC: return "TFC";
425     case TFS: return "TFS";
426     case TFH: return "TFH";
427     case BCH: return "BCH";
428     case ASM: return "ASM";
429   }
430   // clang-format on
431   UNREACHABLE();
432 }
433 
434 // static
IsCpp(int index)435 bool Builtins::IsCpp(int index) { return Builtins::KindOf(index) == CPP; }
436 
437 // static
AllowDynamicFunction(Isolate * isolate,Handle<JSFunction> target,Handle<JSObject> target_global_proxy)438 bool Builtins::AllowDynamicFunction(Isolate* isolate, Handle<JSFunction> target,
439                                     Handle<JSObject> target_global_proxy) {
440   if (FLAG_allow_unsafe_function_constructor) return true;
441   HandleScopeImplementer* impl = isolate->handle_scope_implementer();
442   Handle<Context> responsible_context = impl->LastEnteredOrMicrotaskContext();
443   // TODO(jochen): Remove this.
444   if (responsible_context.is_null()) {
445     return true;
446   }
447   if (*responsible_context == target->context()) return true;
448   return isolate->MayAccess(responsible_context, target_global_proxy);
449 }
450 
451 // static
CodeObjectIsExecutable(int builtin_index)452 bool Builtins::CodeObjectIsExecutable(int builtin_index) {
453   // If the runtime/optimized code always knows when executing a given builtin
454   // that it is a builtin, then that builtin does not need an executable Code
455   // object. Such Code objects can go in read_only_space (and can even be
456   // smaller with no branch instruction), thus saving memory.
457 
458   // Builtins with JS linkage will always have executable Code objects since
459   // they can be called directly from jitted code with no way of determining
460   // that they are builtins at generation time. E.g.
461   //   f = Array.of;
462   //   f(1, 2, 3);
463   // TODO(delphick): This is probably too loose but for now Wasm can call any JS
464   // linkage builtin via its Code object. Once Wasm is fixed this can either be
465   // tighted or removed completely.
466   if (Builtins::KindOf(builtin_index) != BCH && HasJSLinkage(builtin_index)) {
467     return true;
468   }
469 
470   // There are some other non-TF builtins that also have JS linkage like
471   // InterpreterEntryTrampoline which are explicitly allow-listed below.
472   // TODO(delphick): Some of these builtins do not fit with the above, but
473   // currently cause problems if they're not executable. This list should be
474   // pared down as much as possible.
475   switch (builtin_index) {
476     case Builtins::kInterpreterEntryTrampoline:
477     case Builtins::kCompileLazy:
478     case Builtins::kCompileLazyDeoptimizedCode:
479     case Builtins::kCallFunction_ReceiverIsNullOrUndefined:
480     case Builtins::kCallFunction_ReceiverIsNotNullOrUndefined:
481     case Builtins::kCallFunction_ReceiverIsAny:
482     case Builtins::kCallBoundFunction:
483     case Builtins::kCall_ReceiverIsNullOrUndefined:
484     case Builtins::kCall_ReceiverIsNotNullOrUndefined:
485     case Builtins::kCall_ReceiverIsAny:
486     case Builtins::kArgumentsAdaptorTrampoline:
487     case Builtins::kHandleApiCall:
488     case Builtins::kInstantiateAsmJs:
489     case Builtins::kGenericJSToWasmWrapper:
490 
491     // TODO(delphick): Remove this when calls to it have the trampoline inlined
492     // or are converted to use kCallBuiltinPointer.
493     case Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit:
494       return true;
495     default:
496 #if V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
497       // TODO(Loongson): Move non-JS linkage builtins code objects into RO_SPACE
498       // caused MIPS platform to crash, and we need some time to handle it. Now
499       // disable this change temporarily on MIPS platform.
500       return true;
501 #else
502       return false;
503 #endif  // V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
504   }
505 }
506 
ExampleBuiltinForTorqueFunctionPointerType(size_t function_pointer_type_id)507 Builtins::Name ExampleBuiltinForTorqueFunctionPointerType(
508     size_t function_pointer_type_id) {
509   switch (function_pointer_type_id) {
510 #define FUNCTION_POINTER_ID_CASE(id, name) \
511   case id:                                 \
512     return Builtins::k##name;
513     TORQUE_FUNCTION_POINTER_TYPE_TO_BUILTIN_MAP(FUNCTION_POINTER_ID_CASE)
514 #undef FUNCTION_POINTER_ID_CASE
515     default:
516       UNREACHABLE();
517   }
518 }
519 
520 }  // namespace internal
521 }  // namespace v8
522