• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 //     * Redistributions of source code must retain the above copyright
7 //       notice, this list of conditions and the following disclaimer.
8 //     * Redistributions in binary form must reproduce the above
9 //       copyright notice, this list of conditions and the following
10 //       disclaimer in the documentation and/or other materials provided
11 //       with the distribution.
12 //     * Neither the name of Google Inc. nor the names of its
13 //       contributors may be used to endorse or promote products derived
14 //       from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 
28 #include "src/diagnostics/perf-jit.h"
29 
30 // Only compile the {PerfJitLogger} on Linux.
31 #if V8_OS_LINUX
32 
33 #include <fcntl.h>
34 #include <sys/mman.h>
35 #include <unistd.h>
36 #include <memory>
37 
38 #include "src/codegen/assembler.h"
39 #include "src/codegen/source-position-table.h"
40 #include "src/diagnostics/eh-frame.h"
41 #include "src/objects/objects-inl.h"
42 #include "src/objects/shared-function-info.h"
43 #include "src/snapshot/embedded/embedded-data.h"
44 #include "src/utils/ostreams.h"
45 #include "src/wasm/wasm-code-manager.h"
46 
47 namespace v8 {
48 namespace internal {
49 
50 struct PerfJitHeader {
51   uint32_t magic_;
52   uint32_t version_;
53   uint32_t size_;
54   uint32_t elf_mach_target_;
55   uint32_t reserved_;
56   uint32_t process_id_;
57   uint64_t time_stamp_;
58   uint64_t flags_;
59 
60   static const uint32_t kMagic = 0x4A695444;
61   static const uint32_t kVersion = 1;
62 };
63 
64 struct PerfJitBase {
65   enum PerfJitEvent {
66     kLoad = 0,
67     kMove = 1,
68     kDebugInfo = 2,
69     kClose = 3,
70     kUnwindingInfo = 4
71   };
72 
73   uint32_t event_;
74   uint32_t size_;
75   uint64_t time_stamp_;
76 };
77 
78 struct PerfJitCodeLoad : PerfJitBase {
79   uint32_t process_id_;
80   uint32_t thread_id_;
81   uint64_t vma_;
82   uint64_t code_address_;
83   uint64_t code_size_;
84   uint64_t code_id_;
85 };
86 
87 struct PerfJitDebugEntry {
88   uint64_t address_;
89   int line_number_;
90   int column_;
91   // Followed by null-terminated name or \0xFF\0 if same as previous.
92 };
93 
94 struct PerfJitCodeDebugInfo : PerfJitBase {
95   uint64_t address_;
96   uint64_t entry_count_;
97   // Followed by entry_count_ instances of PerfJitDebugEntry.
98 };
99 
100 struct PerfJitCodeUnwindingInfo : PerfJitBase {
101   uint64_t unwinding_size_;
102   uint64_t eh_frame_hdr_size_;
103   uint64_t mapped_size_;
104   // Followed by size_ - sizeof(PerfJitCodeUnwindingInfo) bytes of data.
105 };
106 
107 const char PerfJitLogger::kFilenameFormatString[] = "./jit-%d.dump";
108 
109 // Extra padding for the PID in the filename
110 const int PerfJitLogger::kFilenameBufferPadding = 16;
111 
112 base::LazyRecursiveMutex PerfJitLogger::file_mutex_;
113 // The following static variables are protected by PerfJitLogger::file_mutex_.
114 uint64_t PerfJitLogger::reference_count_ = 0;
115 void* PerfJitLogger::marker_address_ = nullptr;
116 uint64_t PerfJitLogger::code_index_ = 0;
117 FILE* PerfJitLogger::perf_output_handle_ = nullptr;
118 
OpenJitDumpFile()119 void PerfJitLogger::OpenJitDumpFile() {
120   // Open the perf JIT dump file.
121   perf_output_handle_ = nullptr;
122 
123   int bufferSize = sizeof(kFilenameFormatString) + kFilenameBufferPadding;
124   ScopedVector<char> perf_dump_name(bufferSize);
125   int size = SNPrintF(perf_dump_name, kFilenameFormatString,
126                       base::OS::GetCurrentProcessId());
127   CHECK_NE(size, -1);
128 
129   int fd = open(perf_dump_name.begin(), O_CREAT | O_TRUNC | O_RDWR, 0666);
130   if (fd == -1) return;
131 
132   // If --perf-prof-delete-file is given, unlink the file right after opening
133   // it. This keeps the file handle to the file valid. This only works on Linux,
134   // which is the only platform supported for --perf-prof anyway.
135   if (FLAG_perf_prof_delete_file) CHECK_EQ(0, unlink(perf_dump_name.begin()));
136 
137   marker_address_ = OpenMarkerFile(fd);
138   if (marker_address_ == nullptr) return;
139 
140   perf_output_handle_ = fdopen(fd, "w+");
141   if (perf_output_handle_ == nullptr) return;
142 
143   setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize);
144 }
145 
CloseJitDumpFile()146 void PerfJitLogger::CloseJitDumpFile() {
147   if (perf_output_handle_ == nullptr) return;
148   fclose(perf_output_handle_);
149   perf_output_handle_ = nullptr;
150 }
151 
OpenMarkerFile(int fd)152 void* PerfJitLogger::OpenMarkerFile(int fd) {
153   long page_size = sysconf(_SC_PAGESIZE);  // NOLINT(runtime/int)
154   if (page_size == -1) return nullptr;
155 
156   // Mmap the file so that there is a mmap record in the perf_data file.
157   //
158   // The map must be PROT_EXEC to ensure it is not ignored by perf record.
159   void* marker_address =
160       mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
161   return (marker_address == MAP_FAILED) ? nullptr : marker_address;
162 }
163 
CloseMarkerFile(void * marker_address)164 void PerfJitLogger::CloseMarkerFile(void* marker_address) {
165   if (marker_address == nullptr) return;
166   long page_size = sysconf(_SC_PAGESIZE);  // NOLINT(runtime/int)
167   if (page_size == -1) return;
168   munmap(marker_address, page_size);
169 }
170 
PerfJitLogger(Isolate * isolate)171 PerfJitLogger::PerfJitLogger(Isolate* isolate) : CodeEventLogger(isolate) {
172   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
173 
174   reference_count_++;
175   // If this is the first logger, open the file and write the header.
176   if (reference_count_ == 1) {
177     OpenJitDumpFile();
178     if (perf_output_handle_ == nullptr) return;
179     LogWriteHeader();
180   }
181 }
182 
~PerfJitLogger()183 PerfJitLogger::~PerfJitLogger() {
184   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
185 
186   reference_count_--;
187   // If this was the last logger, close the file.
188   if (reference_count_ == 0) {
189     CloseJitDumpFile();
190   }
191 }
192 
GetTimestamp()193 uint64_t PerfJitLogger::GetTimestamp() {
194   struct timespec ts;
195   int result = clock_gettime(CLOCK_MONOTONIC, &ts);
196   DCHECK_EQ(0, result);
197   USE(result);
198   static const uint64_t kNsecPerSec = 1000000000;
199   return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec;
200 }
201 
LogRecordedBuffer(Handle<AbstractCode> abstract_code,MaybeHandle<SharedFunctionInfo> maybe_shared,const char * name,int length)202 void PerfJitLogger::LogRecordedBuffer(
203     Handle<AbstractCode> abstract_code,
204     MaybeHandle<SharedFunctionInfo> maybe_shared, const char* name,
205     int length) {
206   if (FLAG_perf_basic_prof_only_functions &&
207       (abstract_code->kind() != CodeKind::INTERPRETED_FUNCTION &&
208        abstract_code->kind() != CodeKind::TURBOFAN &&
209        abstract_code->kind() != CodeKind::NATIVE_CONTEXT_INDEPENDENT &&
210        abstract_code->kind() != CodeKind::TURBOPROP)) {
211     return;
212   }
213 
214   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
215 
216   if (perf_output_handle_ == nullptr) return;
217 
218   // We only support non-interpreted functions.
219   if (!abstract_code->IsCode()) return;
220   Handle<Code> code = Handle<Code>::cast(abstract_code);
221   DCHECK(code->raw_instruction_start() == code->address() + Code::kHeaderSize);
222 
223   // Debug info has to be emitted first.
224   Handle<SharedFunctionInfo> shared;
225   if (FLAG_perf_prof && !maybe_shared.ToHandle(&shared)) {
226     // TODO(herhut): This currently breaks for js2wasm/wasm2js functions.
227     if (code->kind() != CodeKind::JS_TO_WASM_FUNCTION &&
228         code->kind() != CodeKind::WASM_TO_JS_FUNCTION) {
229       LogWriteDebugInfo(code, shared);
230     }
231   }
232 
233   const char* code_name = name;
234   uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code->InstructionStart());
235 
236   // Unwinding info comes right after debug info.
237   if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(*code);
238 
239   WriteJitCodeLoadEntry(code_pointer, code->InstructionSize(), code_name,
240                         length);
241 }
242 
LogRecordedBuffer(const wasm::WasmCode * code,const char * name,int length)243 void PerfJitLogger::LogRecordedBuffer(const wasm::WasmCode* code,
244                                       const char* name, int length) {
245   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
246 
247   if (perf_output_handle_ == nullptr) return;
248 
249   if (FLAG_perf_prof_annotate_wasm) {
250     LogWriteDebugInfo(code);
251   }
252 
253   WriteJitCodeLoadEntry(code->instructions().begin(),
254                         code->instructions().length(), name, length);
255 }
256 
WriteJitCodeLoadEntry(const uint8_t * code_pointer,uint32_t code_size,const char * name,int name_length)257 void PerfJitLogger::WriteJitCodeLoadEntry(const uint8_t* code_pointer,
258                                           uint32_t code_size, const char* name,
259                                           int name_length) {
260   static const char string_terminator[] = "\0";
261 
262   PerfJitCodeLoad code_load;
263   code_load.event_ = PerfJitCodeLoad::kLoad;
264   code_load.size_ = sizeof(code_load) + name_length + 1 + code_size;
265   code_load.time_stamp_ = GetTimestamp();
266   code_load.process_id_ =
267       static_cast<uint32_t>(base::OS::GetCurrentProcessId());
268   code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId());
269   code_load.vma_ = reinterpret_cast<uint64_t>(code_pointer);
270   code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer);
271   code_load.code_size_ = code_size;
272   code_load.code_id_ = code_index_;
273 
274   code_index_++;
275 
276   LogWriteBytes(reinterpret_cast<const char*>(&code_load), sizeof(code_load));
277   LogWriteBytes(name, name_length);
278   LogWriteBytes(string_terminator, 1);
279   LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size);
280 }
281 
282 namespace {
283 
284 constexpr char kUnknownScriptNameString[] = "<unknown>";
285 constexpr size_t kUnknownScriptNameStringLen =
286     arraysize(kUnknownScriptNameString) - 1;
287 
GetScriptNameLength(const SourcePositionInfo & info)288 size_t GetScriptNameLength(const SourcePositionInfo& info) {
289   if (!info.script.is_null()) {
290     Object name_or_url = info.script->GetNameOrSourceURL();
291     if (name_or_url.IsString()) {
292       String str = String::cast(name_or_url);
293       if (str.IsOneByteRepresentation()) return str.length();
294       int length;
295       str.ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
296       return static_cast<size_t>(length);
297     }
298   }
299   return kUnknownScriptNameStringLen;
300 }
301 
GetScriptName(const SourcePositionInfo & info,std::unique_ptr<char[]> * storage,const DisallowHeapAllocation & no_gc)302 Vector<const char> GetScriptName(const SourcePositionInfo& info,
303                                  std::unique_ptr<char[]>* storage,
304                                  const DisallowHeapAllocation& no_gc) {
305   if (!info.script.is_null()) {
306     Object name_or_url = info.script->GetNameOrSourceURL();
307     if (name_or_url.IsSeqOneByteString()) {
308       SeqOneByteString str = SeqOneByteString::cast(name_or_url);
309       return {reinterpret_cast<char*>(str.GetChars(no_gc)),
310               static_cast<size_t>(str.length())};
311     } else if (name_or_url.IsString()) {
312       int length;
313       *storage = String::cast(name_or_url)
314                      .ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
315       return {storage->get(), static_cast<size_t>(length)};
316     }
317   }
318   return {kUnknownScriptNameString, kUnknownScriptNameStringLen};
319 }
320 
GetSourcePositionInfo(Handle<Code> code,Handle<SharedFunctionInfo> function,SourcePosition pos)321 SourcePositionInfo GetSourcePositionInfo(Handle<Code> code,
322                                          Handle<SharedFunctionInfo> function,
323                                          SourcePosition pos) {
324   if (code->is_turbofanned()) {
325     DisallowHeapAllocation disallow;
326     return pos.InliningStack(code)[0];
327   } else {
328     return SourcePositionInfo(pos, function);
329   }
330 }
331 
332 }  // namespace
333 
LogWriteDebugInfo(Handle<Code> code,Handle<SharedFunctionInfo> shared)334 void PerfJitLogger::LogWriteDebugInfo(Handle<Code> code,
335                                       Handle<SharedFunctionInfo> shared) {
336   // Compute the entry count and get the name of the script.
337   uint32_t entry_count = 0;
338   for (SourcePositionTableIterator iterator(code->SourcePositionTable());
339        !iterator.done(); iterator.Advance()) {
340     entry_count++;
341   }
342   if (entry_count == 0) return;
343   // The WasmToJS wrapper stubs have source position entries.
344   if (!shared->HasSourceCode()) return;
345   Handle<Script> script(Script::cast(shared->script()), isolate_);
346 
347   PerfJitCodeDebugInfo debug_info;
348 
349   debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
350   debug_info.time_stamp_ = GetTimestamp();
351   debug_info.address_ = code->InstructionStart();
352   debug_info.entry_count_ = entry_count;
353 
354   uint32_t size = sizeof(debug_info);
355   // Add the sizes of fixed parts of entries.
356   size += entry_count * sizeof(PerfJitDebugEntry);
357   // Add the size of the name after each entry.
358 
359   for (SourcePositionTableIterator iterator(code->SourcePositionTable());
360        !iterator.done(); iterator.Advance()) {
361     SourcePositionInfo info(
362         GetSourcePositionInfo(code, shared, iterator.source_position()));
363     size += GetScriptNameLength(info) + 1;
364   }
365 
366   int padding = ((size + 7) & (~7)) - size;
367   debug_info.size_ = size + padding;
368   LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
369 
370   Address code_start = code->InstructionStart();
371 
372   for (SourcePositionTableIterator iterator(code->SourcePositionTable());
373        !iterator.done(); iterator.Advance()) {
374     SourcePositionInfo info(
375         GetSourcePositionInfo(code, shared, iterator.source_position()));
376     PerfJitDebugEntry entry;
377     // The entry point of the function will be placed straight after the ELF
378     // header when processed by "perf inject". Adjust the position addresses
379     // accordingly.
380     entry.address_ = code_start + iterator.code_offset() + kElfHeaderSize;
381     entry.line_number_ = info.line + 1;
382     entry.column_ = info.column + 1;
383     LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
384     // The extracted name may point into heap-objects, thus disallow GC.
385     DisallowHeapAllocation no_gc;
386     std::unique_ptr<char[]> name_storage;
387     Vector<const char> name_string = GetScriptName(info, &name_storage, no_gc);
388     LogWriteBytes(name_string.begin(),
389                   static_cast<uint32_t>(name_string.size()) + 1);
390   }
391   char padding_bytes[8] = {0};
392   LogWriteBytes(padding_bytes, padding);
393 }
394 
LogWriteDebugInfo(const wasm::WasmCode * code)395 void PerfJitLogger::LogWriteDebugInfo(const wasm::WasmCode* code) {
396   wasm::WasmModuleSourceMap* source_map =
397       code->native_module()->GetWasmSourceMap();
398   wasm::WireBytesRef code_ref =
399       code->native_module()->module()->functions[code->index()].code;
400   uint32_t code_offset = code_ref.offset();
401   uint32_t code_end_offset = code_ref.end_offset();
402 
403   uint32_t entry_count = 0;
404   uint32_t size = 0;
405 
406   if (!source_map || !source_map->IsValid() ||
407       !source_map->HasSource(code_offset, code_end_offset)) {
408     return;
409   }
410 
411   for (SourcePositionTableIterator iterator(code->source_positions());
412        !iterator.done(); iterator.Advance()) {
413     uint32_t offset = iterator.source_position().ScriptOffset() + code_offset;
414     if (!source_map->HasValidEntry(code_offset, offset)) continue;
415     entry_count++;
416     size += source_map->GetFilename(offset).size() + 1;
417   }
418 
419   if (entry_count == 0) return;
420 
421   PerfJitCodeDebugInfo debug_info;
422 
423   debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
424   debug_info.time_stamp_ = GetTimestamp();
425   debug_info.address_ =
426       reinterpret_cast<uintptr_t>(code->instructions().begin());
427   debug_info.entry_count_ = entry_count;
428 
429   size += sizeof(debug_info);
430   // Add the sizes of fixed parts of entries.
431   size += entry_count * sizeof(PerfJitDebugEntry);
432 
433   int padding = ((size + 7) & (~7)) - size;
434   debug_info.size_ = size + padding;
435   LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
436 
437   uintptr_t code_begin =
438       reinterpret_cast<uintptr_t>(code->instructions().begin());
439 
440   for (SourcePositionTableIterator iterator(code->source_positions());
441        !iterator.done(); iterator.Advance()) {
442     uint32_t offset = iterator.source_position().ScriptOffset() + code_offset;
443     if (!source_map->HasValidEntry(code_offset, offset)) continue;
444     PerfJitDebugEntry entry;
445     // The entry point of the function will be placed straight after the ELF
446     // header when processed by "perf inject". Adjust the position addresses
447     // accordingly.
448     entry.address_ = code_begin + iterator.code_offset() + kElfHeaderSize;
449     entry.line_number_ =
450         static_cast<int>(source_map->GetSourceLine(offset)) + 1;
451     entry.column_ = 1;
452     LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
453     std::string name_string = source_map->GetFilename(offset);
454     LogWriteBytes(name_string.c_str(),
455                   static_cast<int>(name_string.size() + 1));
456   }
457 
458   char padding_bytes[8] = {0};
459   LogWriteBytes(padding_bytes, padding);
460 }
461 
LogWriteUnwindingInfo(Code code)462 void PerfJitLogger::LogWriteUnwindingInfo(Code code) {
463   PerfJitCodeUnwindingInfo unwinding_info_header;
464   unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo;
465   unwinding_info_header.time_stamp_ = GetTimestamp();
466   unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize;
467 
468   if (code.has_unwinding_info()) {
469     unwinding_info_header.unwinding_size_ = code.unwinding_info_size();
470     unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_;
471   } else {
472     unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize;
473     unwinding_info_header.mapped_size_ = 0;
474   }
475 
476   int content_size = static_cast<int>(sizeof(unwinding_info_header) +
477                                       unwinding_info_header.unwinding_size_);
478   int padding_size = RoundUp(content_size, 8) - content_size;
479   unwinding_info_header.size_ = content_size + padding_size;
480 
481   LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header),
482                 sizeof(unwinding_info_header));
483 
484   if (code.has_unwinding_info()) {
485     LogWriteBytes(reinterpret_cast<const char*>(code.unwinding_info_start()),
486                   code.unwinding_info_size());
487   } else {
488     OFStream perf_output_stream(perf_output_handle_);
489     EhFrameWriter::WriteEmptyEhFrame(perf_output_stream);
490   }
491 
492   char padding_bytes[] = "\0\0\0\0\0\0\0\0";
493   DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes)));
494   LogWriteBytes(padding_bytes, static_cast<int>(padding_size));
495 }
496 
CodeMoveEvent(AbstractCode from,AbstractCode to)497 void PerfJitLogger::CodeMoveEvent(AbstractCode from, AbstractCode to) {
498   // We may receive a CodeMove event if a BytecodeArray object moves. Otherwise
499   // code relocation is not supported.
500   CHECK(from.IsBytecodeArray());
501 }
502 
LogWriteBytes(const char * bytes,int size)503 void PerfJitLogger::LogWriteBytes(const char* bytes, int size) {
504   size_t rv = fwrite(bytes, 1, size, perf_output_handle_);
505   DCHECK(static_cast<size_t>(size) == rv);
506   USE(rv);
507 }
508 
LogWriteHeader()509 void PerfJitLogger::LogWriteHeader() {
510   DCHECK_NOT_NULL(perf_output_handle_);
511   PerfJitHeader header;
512 
513   header.magic_ = PerfJitHeader::kMagic;
514   header.version_ = PerfJitHeader::kVersion;
515   header.size_ = sizeof(header);
516   header.elf_mach_target_ = GetElfMach();
517   header.reserved_ = 0xDEADBEEF;
518   header.process_id_ = base::OS::GetCurrentProcessId();
519   header.time_stamp_ =
520       static_cast<uint64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis() *
521                             base::Time::kMicrosecondsPerMillisecond);
522   header.flags_ = 0;
523 
524   LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header));
525 }
526 
527 }  // namespace internal
528 }  // namespace v8
529 
530 #endif  // V8_OS_LINUX
531