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