summaryrefslogtreecommitdiff
path: root/deps/v8/src/diagnostics/unwinding-info-win64.cc
blob: 6cc53da51f16a45c3a8ad14ca0b2150bed43d766 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
// Copyright 2019 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/diagnostics/unwinding-info-win64.h"

#include "src/codegen/macro-assembler.h"
#include "src/utils/allocation.h"

#if defined(V8_OS_WIN_X64)
#include "src/codegen/x64/assembler-x64.h"
#elif defined(V8_OS_WIN_ARM64)
#include "src/codegen/arm64/assembler-arm64-inl.h"
#include "src/codegen/arm64/macro-assembler-arm64-inl.h"
#else
#error "Unsupported OS"
#endif  // V8_OS_WIN_X64

namespace v8 {
namespace internal {
namespace win64_unwindinfo {

bool CanEmitUnwindInfoForBuiltins() { return FLAG_win64_unwinding_info; }

bool CanRegisterUnwindInfoForNonABICompliantCodeRange() {
  return !FLAG_jitless;
}

bool RegisterUnwindInfoForExceptionHandlingOnly() {
  DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
#if defined(V8_OS_WIN_ARM64)
  return !FLAG_win64_unwinding_info;
#else
  return !IsWindows8OrGreater() || !FLAG_win64_unwinding_info;
#endif
}

v8::UnhandledExceptionCallback unhandled_exception_callback_g = nullptr;

void SetUnhandledExceptionCallback(
    v8::UnhandledExceptionCallback unhandled_exception_callback) {
  unhandled_exception_callback_g = unhandled_exception_callback;
}

// This function is registered as exception handler for V8-generated code as
// part of the registration of unwinding info. It is referenced by
// RegisterNonABICompliantCodeRange(), below, and by the unwinding info for
// builtins declared in the embedded blob.
extern "C" __declspec(dllexport) int CRASH_HANDLER_FUNCTION_NAME(
    PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame,
    PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) {
  if (unhandled_exception_callback_g != nullptr) {
    EXCEPTION_POINTERS info = {ExceptionRecord, ContextRecord};
    return unhandled_exception_callback_g(&info);
  }
  return ExceptionContinueSearch;
}

#if defined(V8_OS_WIN_X64)

#pragma pack(push, 1)

/*
 * From Windows SDK ehdata.h, which does not compile with Clang.
 * See https://msdn.microsoft.com/en-us/library/ddssxxy8.aspx.
 */
union UNWIND_CODE {
  struct {
    unsigned char CodeOffset;
    unsigned char UnwindOp : 4;
    unsigned char OpInfo : 4;
  };
  uint16_t FrameOffset;
};

struct UNWIND_INFO {
  unsigned char Version : 3;
  unsigned char Flags : 5;
  unsigned char SizeOfProlog;
  unsigned char CountOfCodes;
  unsigned char FrameRegister : 4;
  unsigned char FrameOffset : 4;
};

static constexpr int kNumberOfUnwindCodes = 2;
static constexpr int kMaxExceptionThunkSize = 12;

struct V8UnwindData {
  UNWIND_INFO unwind_info;
  UNWIND_CODE unwind_codes[kNumberOfUnwindCodes];

  V8UnwindData() {
    static constexpr int kOpPushNonvol = 0;
    static constexpr int kOpSetFPReg = 3;

    unwind_info.Version = 1;
    unwind_info.Flags = UNW_FLAG_EHANDLER;
    unwind_info.SizeOfProlog = kRbpPrefixLength;
    unwind_info.CountOfCodes = kRbpPrefixCodes;
    unwind_info.FrameRegister = rbp.code();
    unwind_info.FrameOffset = 0;

    unwind_codes[0].CodeOffset = kRbpPrefixLength;  // movq rbp, rsp
    unwind_codes[0].UnwindOp = kOpSetFPReg;
    unwind_codes[0].OpInfo = 0;

    unwind_codes[1].CodeOffset = kPushRbpInstructionLength;  // push rbp
    unwind_codes[1].UnwindOp = kOpPushNonvol;
    unwind_codes[1].OpInfo = rbp.code();
  }
};

struct ExceptionHandlerUnwindData {
  UNWIND_INFO unwind_info;

  ExceptionHandlerUnwindData() {
    unwind_info.Version = 1;
    unwind_info.Flags = UNW_FLAG_EHANDLER;
    unwind_info.SizeOfProlog = 0;
    unwind_info.CountOfCodes = 0;
    unwind_info.FrameRegister = 0;
    unwind_info.FrameOffset = 0;
  }
};

struct CodeRangeUnwindingRecord {
  void* dynamic_table;
  uint32_t runtime_function_count;
  V8UnwindData unwind_info;
  uint32_t exception_handler;
  uint8_t exception_thunk[kMaxExceptionThunkSize];
  RUNTIME_FUNCTION runtime_function[kDefaultRuntimeFunctionCount];
};

struct ExceptionHandlerRecord {
  uint32_t runtime_function_count;
  RUNTIME_FUNCTION runtime_function[kDefaultRuntimeFunctionCount];
  ExceptionHandlerUnwindData unwind_info;
  uint32_t exception_handler;
  uint8_t exception_thunk[kMaxExceptionThunkSize];
};

#pragma pack(pop)

std::vector<uint8_t> GetUnwindInfoForBuiltinFunctions() {
  V8UnwindData xdata;
  return std::vector<uint8_t>(
      reinterpret_cast<uint8_t*>(&xdata),
      reinterpret_cast<uint8_t*>(&xdata) + sizeof(xdata));
}

template <typename Record>
void InitUnwindingRecord(Record* record, size_t code_size_in_bytes) {
  // We assume that the first page of the code range is executable and
  // committed and reserved to contain PDATA/XDATA.

  // All addresses are 32bit relative offsets to start.
  record->runtime_function[0].BeginAddress = 0;
  record->runtime_function[0].EndAddress =
      static_cast<DWORD>(code_size_in_bytes);
  record->runtime_function[0].UnwindData = offsetof(Record, unwind_info);
  record->runtime_function_count = 1;
  record->exception_handler = offsetof(Record, exception_thunk);

  // Hardcoded thunk.
  AssemblerOptions options;
  options.record_reloc_info_for_serialization = false;
  MacroAssembler masm(nullptr, options, CodeObjectRequired::kNo,
                      NewAssemblerBuffer(64));
  masm.movq(rax, reinterpret_cast<uint64_t>(&CRASH_HANDLER_FUNCTION_NAME));
  masm.jmp(rax);
  DCHECK_LE(masm.instruction_size(), sizeof(record->exception_thunk));
  memcpy(&record->exception_thunk[0], masm.buffer_start(),
         masm.instruction_size());
}

#elif defined(V8_OS_WIN_ARM64)

#pragma pack(push, 1)

// ARM64 unwind codes are defined in below doc.
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
enum UnwindOp8Bit {
  OpNop = 0xE3,
  OpSaveFpLr = 0x40,
  OpSaveFpLrX = 0x80,
  OpSetFp = 0xE1,
  OpEnd = 0xE4,
};

typedef uint32_t UNWIND_CODE;

constexpr UNWIND_CODE Combine8BitUnwindCodes(uint8_t code0,
                                             uint8_t code1 = OpNop,
                                             uint8_t code2 = OpNop,
                                             uint8_t code3 = OpNop) {
  return static_cast<uint32_t>(code0) | (static_cast<uint32_t>(code1) << 8) |
         (static_cast<uint32_t>(code2) << 16) |
         (static_cast<uint32_t>(code3) << 24);
}

// UNWIND_INFO defines the static part (first 32-bit) of the .xdata record in
// below doc.
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#xdata-records
struct UNWIND_INFO {
  uint32_t FunctionLength : 18;
  uint32_t Version : 2;
  uint32_t X : 1;
  uint32_t E : 1;
  uint32_t EpilogCount : 5;
  uint32_t CodeWords : 5;
};

static constexpr int kNumberOfUnwindCodes = 1;
static constexpr int kMaxExceptionThunkSize = 16;
static constexpr int kFunctionLengthShiftSize = 2;
static constexpr int kFunctionLengthMask = (1 << kFunctionLengthShiftSize) - 1;
static constexpr int kFramePointerAdjustmentShiftSize = 3;
static constexpr int kFramePointerAdjustmentShiftMask =
    (1 << kFramePointerAdjustmentShiftSize) - 1;

struct V8UnwindData {
  UNWIND_INFO unwind_info;
  UNWIND_CODE unwind_codes[kNumberOfUnwindCodes];

  V8UnwindData() {
    memset(&unwind_info, 0, sizeof(UNWIND_INFO));
    unwind_info.X = 1;  // has exception handler after unwind-codes.
    unwind_info.CodeWords = 1;

    // stp fp, lr, [sp, #offset]!
    unwind_codes[0] = Combine8BitUnwindCodes(OpSetFp, OpSaveFpLrX, OpEnd);
  }
};

struct CodeRangeUnwindingRecord {
  void* dynamic_table;
  uint32_t runtime_function_count;
  V8UnwindData unwind_info;
  uint32_t exception_handler;

  // For Windows ARM64 unwinding, register 2 unwind_info for each code range,
  // unwind_info for all full size ranges (1MB - 4 bytes) and unwind_info1 for
  // the remaining non full size range. There is at most 1 range which is less
  // than full size.
  V8UnwindData unwind_info1;
  uint32_t exception_handler1;
  uint8_t exception_thunk[kMaxExceptionThunkSize];

  // More RUNTIME_FUNCTION structs could follow below array because the number
  // of RUNTIME_FUNCTION needed to cover given code range is computed at
  // runtime.
  RUNTIME_FUNCTION runtime_function[kDefaultRuntimeFunctionCount];
};

#pragma pack(pop)

std::vector<uint8_t> GetUnwindInfoForBuiltinFunction(uint32_t func_len,
                                                     int32_t fp_adjustment) {
  DCHECK_LE(func_len, kMaxFunctionLength);
  DCHECK_EQ((func_len & kFunctionLengthMask), 0);
  USE(kFunctionLengthMask);

  // Unwind code save_fplr requires the offset to be within range [0, 504].
  // This range is defined in below doc for unwind code save_fplr.
  // https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
  DCHECK_GE(fp_adjustment, 0);
  DCHECK_LE(fp_adjustment, 504);
  DCHECK_EQ((fp_adjustment & kFramePointerAdjustmentShiftMask), 0);
  USE(kFramePointerAdjustmentShiftMask);

  V8UnwindData xdata;
  // FunctionLength is ensured to be aligned at instruction size and Windows
  // ARM64 doesn't encoding its 2 LSB.
  xdata.unwind_info.FunctionLength = func_len >> kFunctionLengthShiftSize;
  xdata.unwind_info.CodeWords = 1;
  xdata.unwind_codes[0] = Combine8BitUnwindCodes(
      OpSetFp,
      (OpSaveFpLr | (fp_adjustment >> kFramePointerAdjustmentShiftSize)),
      OpEnd);

  return std::vector<uint8_t>(
      reinterpret_cast<uint8_t*>(&xdata),
      reinterpret_cast<uint8_t*>(&xdata) + sizeof(xdata));
}

template <typename Record>
void InitUnwindingRecord(Record* record, size_t code_size_in_bytes) {
  // We assume that the first page of the code range is executable and
  // committed and reserved to contain multiple PDATA/XDATA to cover the whole
  // range. All addresses are 32bit relative offsets to start.

  // Maximum RUNTIME_FUNCTION count available in reserved memory, this includes
  // static part in Record as kDefaultRuntimeFunctionCount plus dynamic part in
  // the remaining reserved memory.
  constexpr uint32_t max_runtime_function_count = static_cast<uint32_t>(
      (kOSPageSize - sizeof(Record)) / sizeof(RUNTIME_FUNCTION) +
      kDefaultRuntimeFunctionCount);

  uint32_t runtime_function_index = 0;
  uint32_t current_unwind_start_address = 0;
  int64_t remaining_size_in_bytes = static_cast<int64_t>(code_size_in_bytes);

  // Divide the code range into chunks in size kMaxFunctionLength and create a
  // RUNTIME_FUNCTION for each of them. All the chunks in the same size can
  // share 1 unwind_info struct, but a separate unwind_info is needed for the
  // last chunk if it is smaller than kMaxFunctionLength, because unlike X64,
  // unwind_info encodes the function/chunk length.
  while (remaining_size_in_bytes >= kMaxFunctionLength &&
         runtime_function_index < max_runtime_function_count) {
    record->runtime_function[runtime_function_index].BeginAddress =
        current_unwind_start_address;
    record->runtime_function[runtime_function_index].UnwindData =
        static_cast<DWORD>(offsetof(Record, unwind_info));

    runtime_function_index++;
    current_unwind_start_address += kMaxFunctionLength;
    remaining_size_in_bytes -= kMaxFunctionLength;
  }
  // FunctionLength is ensured to be aligned at instruction size and Windows
  // ARM64 doesn't encoding 2 LSB.
  record->unwind_info.unwind_info.FunctionLength = kMaxFunctionLength >> 2;

  if (remaining_size_in_bytes > 0 &&
      runtime_function_index < max_runtime_function_count) {
    DCHECK_EQ(remaining_size_in_bytes % kInstrSize, 0);

    record->unwind_info1.unwind_info.FunctionLength = static_cast<uint32_t>(
        remaining_size_in_bytes >> kFunctionLengthShiftSize);
    record->runtime_function[runtime_function_index].BeginAddress =
        current_unwind_start_address;
    record->runtime_function[runtime_function_index].UnwindData =
        static_cast<DWORD>(offsetof(Record, unwind_info1));

    remaining_size_in_bytes -= kMaxFunctionLength;
    record->exception_handler1 = offsetof(Record, exception_thunk);
    record->runtime_function_count = runtime_function_index + 1;
  } else {
    record->runtime_function_count = runtime_function_index;
  }

  // 1 page can cover kMaximalCodeRangeSize for ARM64 (128MB). If
  // kMaximalCodeRangeSize is changed for ARM64 and makes 1 page insufficient to
  // cover it, more pages will need to reserved for unwind data.
  DCHECK_LE(remaining_size_in_bytes, 0);

  record->exception_handler = offsetof(Record, exception_thunk);

  // Hardcoded thunk.
  AssemblerOptions options;
  options.record_reloc_info_for_serialization = false;
  TurboAssembler masm(nullptr, options, CodeObjectRequired::kNo,
                      NewAssemblerBuffer(64));
  masm.Mov(x16,
           Operand(reinterpret_cast<uint64_t>(&CRASH_HANDLER_FUNCTION_NAME)));
  masm.Br(x16);
  DCHECK_LE(masm.instruction_size(), sizeof(record->exception_thunk));
  memcpy(&record->exception_thunk[0], masm.buffer_start(),
         masm.instruction_size());
}

#endif  // V8_OS_WIN_X64

namespace {

V8_DECLARE_ONCE(load_ntdll_unwinding_functions_once);
static decltype(
    &::RtlAddGrowableFunctionTable) add_growable_function_table_func = nullptr;
static decltype(
    &::RtlDeleteGrowableFunctionTable) delete_growable_function_table_func =
    nullptr;

void LoadNtdllUnwindingFunctions() {
  base::CallOnce(&load_ntdll_unwinding_functions_once, []() {
    // Load functions from the ntdll.dll module.
    HMODULE ntdll_module =
        LoadLibraryEx(L"ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
    DCHECK_NOT_NULL(ntdll_module);

    // This fails on Windows 7.
    add_growable_function_table_func =
        reinterpret_cast<decltype(&::RtlAddGrowableFunctionTable)>(
            ::GetProcAddress(ntdll_module, "RtlAddGrowableFunctionTable"));
    DCHECK_IMPLIES(IsWindows8OrGreater(), add_growable_function_table_func);

    delete_growable_function_table_func =
        reinterpret_cast<decltype(&::RtlDeleteGrowableFunctionTable)>(
            ::GetProcAddress(ntdll_module, "RtlDeleteGrowableFunctionTable"));
    DCHECK_IMPLIES(IsWindows8OrGreater(), delete_growable_function_table_func);
  });
}

bool AddGrowableFunctionTable(PVOID* DynamicTable,
                              PRUNTIME_FUNCTION FunctionTable, DWORD EntryCount,
                              DWORD MaximumEntryCount, ULONG_PTR RangeBase,
                              ULONG_PTR RangeEnd) {
  DCHECK(::IsWindows8OrGreater());

  LoadNtdllUnwindingFunctions();
  DCHECK_NOT_NULL(add_growable_function_table_func);

  *DynamicTable = nullptr;
  DWORD status =
      add_growable_function_table_func(DynamicTable, FunctionTable, EntryCount,
                                       MaximumEntryCount, RangeBase, RangeEnd);
  DCHECK((status == 0 && *DynamicTable != nullptr) ||
         status == 0xC000009A);  // STATUS_INSUFFICIENT_RESOURCES
  return (status == 0);
}

void DeleteGrowableFunctionTable(PVOID dynamic_table) {
  DCHECK(::IsWindows8OrGreater());

  LoadNtdllUnwindingFunctions();
  DCHECK_NOT_NULL(delete_growable_function_table_func);

  delete_growable_function_table_func(dynamic_table);
}

}  // namespace

void RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) {
  DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());

  // When the --win64-unwinding-info flag is set, we call
  // RtlAddGrowableFunctionTable to register unwinding info for the whole code
  // range of an isolate or WASM module. This enables the Windows OS stack
  // unwinder to work correctly with V8-generated code, enabling stack walking
  // in Windows debuggers and performance tools. However, the
  // RtlAddGrowableFunctionTable API is only supported on Windows 8 and above.
  //
  // On Windows 7, or when --win64-unwinding-info is not set, we may still need
  // to call RtlAddFunctionTable to register a custom exception handler passed
  // by the embedder (like Crashpad).

  if (RegisterUnwindInfoForExceptionHandlingOnly()) {
#if defined(V8_OS_WIN_X64)
    // Windows ARM64 starts since 1709 Windows build, no need to have exception
    // handling only unwind info for compatibility.
    if (unhandled_exception_callback_g) {
      ExceptionHandlerRecord* record = new (start) ExceptionHandlerRecord();
      InitUnwindingRecord(record, size_in_bytes);

      CHECK(::RtlAddFunctionTable(record->runtime_function,
                                  kDefaultRuntimeFunctionCount,
                                  reinterpret_cast<DWORD64>(start)));

      // Protect reserved page against modifications.
      DWORD old_protect;
      CHECK(VirtualProtect(start, sizeof(ExceptionHandlerRecord),
                           PAGE_EXECUTE_READ, &old_protect));
    }
#endif  // V8_OS_WIN_X64
  } else {
    CodeRangeUnwindingRecord* record = new (start) CodeRangeUnwindingRecord();
    InitUnwindingRecord(record, size_in_bytes);

    CHECK(AddGrowableFunctionTable(
        &record->dynamic_table, record->runtime_function,
        record->runtime_function_count, record->runtime_function_count,
        reinterpret_cast<DWORD64>(start),
        reinterpret_cast<DWORD64>(reinterpret_cast<uint8_t*>(start) +
                                  size_in_bytes)));

    // Protect reserved page against modifications.
    DWORD old_protect;
    CHECK(VirtualProtect(start, sizeof(CodeRangeUnwindingRecord),
                         PAGE_EXECUTE_READ, &old_protect));
  }
}

void UnregisterNonABICompliantCodeRange(void* start) {
  DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());

  if (RegisterUnwindInfoForExceptionHandlingOnly()) {
#if defined(V8_OS_WIN_X64)
    // Windows ARM64 starts since 1709 Windows build, no need to have exception
    // handling only unwind info for compatibility.
    if (unhandled_exception_callback_g) {
      ExceptionHandlerRecord* record =
          reinterpret_cast<ExceptionHandlerRecord*>(start);
      CHECK(::RtlDeleteFunctionTable(record->runtime_function));
    }
#endif  // V8_OS_WIN_X64
  } else {
    CodeRangeUnwindingRecord* record =
        reinterpret_cast<CodeRangeUnwindingRecord*>(start);
    if (record->dynamic_table) {
      DeleteGrowableFunctionTable(record->dynamic_table);
    }
  }
}

#if defined(V8_OS_WIN_X64)

void XdataEncoder::onPushRbp() {
  current_frame_code_offset_ =
      assembler_.pc_offset() - kPushRbpInstructionLength;
}

void XdataEncoder::onMovRbpRsp() {
  if (current_frame_code_offset_ >= 0 &&
      current_frame_code_offset_ == assembler_.pc_offset() - kRbpPrefixLength) {
    fp_offsets_.push_back(current_frame_code_offset_);
  }
}

#elif defined(V8_OS_WIN_ARM64)

void XdataEncoder::onSaveFpLr() {
  current_frame_code_offset_ = assembler_.pc_offset() - 4;
  fp_offsets_.push_back(current_frame_code_offset_);
  fp_adjustments_.push_back(current_frame_adjustment_);
  if (current_frame_adjustment_ != 0) {
    current_frame_adjustment_ = 0;
  }
}

void XdataEncoder::onFramePointerAdjustment(int bytes) {
  // According to below doc, offset for save_fplr is aligned to pointer size.
  // https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
  DCHECK_EQ((bytes & kPointerAlignmentMask), 0);

  current_frame_adjustment_ = bytes;
}

#endif  // V8_OS_WIN_X64

}  // namespace win64_unwindinfo
}  // namespace internal
}  // namespace v8