blob: 21d544802d848a0301658bcf7f7c0e02dbdb8370 [file] [log] [blame]
// Copyright 2018 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.
#ifndef V8_SNAPSHOT_EMBEDDED_FILE_WRITER_H_
#define V8_SNAPSHOT_EMBEDDED_FILE_WRITER_H_
#include <cstdio>
#include <cstring>
#include "src/globals.h"
#include "src/snapshot/snapshot.h"
#include "src/source-position-table.h"
#if defined(V8_OS_WIN_X64)
#include "src/unwinding-info-win64.h"
#endif
namespace v8 {
namespace internal {
enum DataDirective {
kByte,
kLong,
kQuad,
kOcta,
};
static constexpr char kDefaultEmbeddedVariant[] = "Default";
// The platform-dependent logic for emitting assembly code for the generated
// embedded.S file.
class EmbeddedFileWriter;
class PlatformDependentEmbeddedFileWriter final {
public:
void SetFile(FILE* fp) { fp_ = fp; }
void SectionText();
void SectionData();
void SectionRoData();
void AlignToCodeAlignment();
void AlignToDataAlignment();
void DeclareUint32(const char* name, uint32_t value);
void DeclarePointerToSymbol(const char* name, const char* target);
#if defined(V8_OS_WIN_X64)
void StartPdataSection();
void EndPdataSection();
void StartXdataSection();
void EndXdataSection();
void DeclareExternalFunction(const char* name);
// Emits an RVA (address relative to the module load address) specified as an
// offset from a given symbol.
void DeclareRvaToSymbol(const char* name, uint64_t offset = 0);
#endif
void DeclareLabel(const char* name);
void SourceInfo(int fileid, const char* filename, int line);
void DeclareFunctionBegin(const char* name);
void DeclareFunctionEnd(const char* name);
// Returns the number of printed characters.
int HexLiteral(uint64_t value);
void Comment(const char* string);
void Newline() { fprintf(fp_, "\n"); }
void FilePrologue();
void DeclareExternalFilename(int fileid, const char* filename);
void FileEpilogue();
int IndentedDataDirective(DataDirective directive);
FILE* fp() const { return fp_; }
private:
void DeclareSymbolGlobal(const char* name);
private:
FILE* fp_ = nullptr;
};
// When writing out compiled builtins to a file, we
// Detailed source-code information about builtins can only be obtained by
// registration on the isolate during compilation.
class EmbeddedFileWriterInterface {
public:
// We maintain a database of filenames to synthetic IDs.
virtual int LookupOrAddExternallyCompiledFilename(const char* filename) = 0;
virtual const char* GetExternallyCompiledFilename(int index) const = 0;
virtual int GetExternallyCompiledFilenameCount() const = 0;
// The isolate will call the method below just prior to replacing the
// compiled builtin Code objects with trampolines.
virtual void PrepareBuiltinSourcePositionMap(Builtins* builtins) = 0;
#if defined(V8_OS_WIN_X64)
virtual void SetBuiltinUnwindData(
int builtin_index,
const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) = 0;
#endif
};
// Generates the embedded.S file which is later compiled into the final v8
// binary. Its contents are exported through two symbols:
//
// v8_<variant>_embedded_blob_ (intptr_t):
// a pointer to the start of the embedded blob.
// v8_<variant>_embedded_blob_size_ (uint32_t):
// size of the embedded blob in bytes.
//
// The variant is usually "Default" but can be modified in multisnapshot builds.
class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
public:
int LookupOrAddExternallyCompiledFilename(const char* filename) override {
auto result = external_filenames_.find(filename);
if (result != external_filenames_.end()) {
return result->second;
}
int new_id =
ExternalFilenameIndexToId(static_cast<int>(external_filenames_.size()));
external_filenames_.insert(std::make_pair(filename, new_id));
external_filenames_by_index_.push_back(filename);
DCHECK_EQ(external_filenames_by_index_.size(), external_filenames_.size());
return new_id;
}
const char* GetExternallyCompiledFilename(int fileid) const override {
size_t index = static_cast<size_t>(ExternalFilenameIdToIndex(fileid));
DCHECK_GE(index, 0);
DCHECK_LT(index, external_filenames_by_index_.size());
return external_filenames_by_index_[index];
}
int GetExternallyCompiledFilenameCount() const override {
return static_cast<int>(external_filenames_.size());
}
void PrepareBuiltinSourcePositionMap(Builtins* builtins) override;
#if defined(V8_OS_WIN_X64)
void SetBuiltinUnwindData(
int builtin_index,
const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) override;
#endif
void SetEmbeddedFile(const char* embedded_src_path) {
embedded_src_path_ = embedded_src_path;
}
void SetEmbeddedVariant(const char* embedded_variant) {
embedded_variant_ = embedded_variant;
}
void WriteEmbedded(const i::EmbeddedData* blob) const {
MaybeWriteEmbeddedFile(blob);
}
private:
void MaybeWriteEmbeddedFile(const i::EmbeddedData* blob) const {
if (embedded_src_path_ == nullptr) return;
FILE* fp = GetFileDescriptorOrDie(embedded_src_path_);
PlatformDependentEmbeddedFileWriter writer;
writer.SetFile(fp);
WriteFilePrologue(&writer);
WriteExternalFilenames(&writer);
WriteMetadataSection(&writer, blob);
WriteInstructionStreams(&writer, blob);
WriteFileEpilogue(&writer, blob);
fclose(fp);
}
static FILE* GetFileDescriptorOrDie(const char* filename) {
FILE* fp = v8::base::OS::FOpen(filename, "wb");
if (fp == nullptr) {
i::PrintF("Unable to open file \"%s\" for writing.\n", filename);
exit(1);
}
return fp;
}
void WriteFilePrologue(PlatformDependentEmbeddedFileWriter* w) const {
w->Comment("Autogenerated file. Do not edit.");
w->Newline();
w->FilePrologue();
}
void WriteExternalFilenames(PlatformDependentEmbeddedFileWriter* w) const {
w->Comment(
"Source positions in the embedded blob refer to filenames by id.");
w->Comment("Assembly directives here map the id to a filename.");
w->Newline();
// Write external filenames.
int size = static_cast<int>(external_filenames_by_index_.size());
for (int i = 0; i < size; i++) {
w->DeclareExternalFilename(ExternalFilenameIndexToId(i),
external_filenames_by_index_[i]);
}
}
// Fairly arbitrary but should fit all symbol names.
static constexpr int kTemporaryStringLength = 256;
std::string EmbeddedBlobDataSymbol() const {
i::EmbeddedVector<char, kTemporaryStringLength> embedded_blob_data_symbol;
i::SNPrintF(embedded_blob_data_symbol, "v8_%s_embedded_blob_data_",
embedded_variant_);
return std::string{embedded_blob_data_symbol.begin()};
}
void WriteMetadataSection(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const {
w->Comment("The embedded blob starts here. Metadata comes first, followed");
w->Comment("by builtin instruction streams.");
w->SectionText();
w->AlignToCodeAlignment();
w->DeclareLabel(EmbeddedBlobDataSymbol().c_str());
WriteBinaryContentsAsInlineAssembly(w, blob->data(),
i::EmbeddedData::RawDataOffset());
}
void WriteBuiltin(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob, const int builtin_id) const {
const bool is_default_variant =
std::strcmp(embedded_variant_, kDefaultEmbeddedVariant) == 0;
i::EmbeddedVector<char, kTemporaryStringLength> builtin_symbol;
if (is_default_variant) {
// Create nicer symbol names for the default mode.
i::SNPrintF(builtin_symbol, "Builtins_%s", i::Builtins::name(builtin_id));
} else {
i::SNPrintF(builtin_symbol, "%s_Builtins_%s", embedded_variant_,
i::Builtins::name(builtin_id));
}
// Labels created here will show up in backtraces. We check in
// Isolate::SetEmbeddedBlob that the blob layout remains unchanged, i.e.
// that labels do not insert bytes into the middle of the blob byte
// stream.
w->DeclareFunctionBegin(builtin_symbol.begin());
const std::vector<byte>& current_positions = source_positions_[builtin_id];
// The code below interleaves bytes of assembly code for the builtin
// function with source positions at the appropriate offsets.
Vector<const byte> vpos(current_positions.data(), current_positions.size());
v8::internal::SourcePositionTableIterator positions(
vpos, SourcePositionTableIterator::kExternalOnly);
const uint8_t* data = reinterpret_cast<const uint8_t*>(
blob->InstructionStartOfBuiltin(builtin_id));
uint32_t size = blob->PaddedInstructionSizeOfBuiltin(builtin_id);
uint32_t i = 0;
uint32_t next_offset = static_cast<uint32_t>(
positions.done() ? size : positions.code_offset());
while (i < size) {
if (i == next_offset) {
// Write source directive.
w->SourceInfo(positions.source_position().ExternalFileId(),
GetExternallyCompiledFilename(
positions.source_position().ExternalFileId()),
positions.source_position().ExternalLine());
positions.Advance();
next_offset = static_cast<uint32_t>(
positions.done() ? size : positions.code_offset());
}
CHECK_GE(next_offset, i);
WriteBinaryContentsAsInlineAssembly(w, data + i, next_offset - i);
i = next_offset;
}
w->DeclareFunctionEnd(builtin_symbol.begin());
}
void WriteInstructionStreams(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const {
for (int i = 0; i < i::Builtins::builtin_count; i++) {
if (!blob->ContainsBuiltin(i)) continue;
WriteBuiltin(w, blob, i);
}
w->Newline();
}
void WriteFileEpilogue(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const {
{
i::EmbeddedVector<char, kTemporaryStringLength> embedded_blob_symbol;
i::SNPrintF(embedded_blob_symbol, "v8_%s_embedded_blob_",
embedded_variant_);
w->Comment("Pointer to the beginning of the embedded blob.");
w->SectionData();
w->AlignToDataAlignment();
w->DeclarePointerToSymbol(embedded_blob_symbol.begin(),
EmbeddedBlobDataSymbol().c_str());
w->Newline();
}
{
i::EmbeddedVector<char, kTemporaryStringLength> embedded_blob_size_symbol;
i::SNPrintF(embedded_blob_size_symbol, "v8_%s_embedded_blob_size_",
embedded_variant_);
w->Comment("The size of the embedded blob in bytes.");
w->SectionRoData();
w->AlignToDataAlignment();
w->DeclareUint32(embedded_blob_size_symbol.begin(), blob->size());
w->Newline();
}
#if defined(V8_OS_WIN_X64)
if (win64_unwindinfo::CanEmitUnwindInfoForBuiltins()) {
WriteUnwindInfo(w, blob);
}
#endif
w->FileEpilogue();
}
#if defined(V8_OS_WIN_X64)
std::string BuiltinsUnwindInfoLabel() const;
void WriteUnwindInfo(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const;
void WriteUnwindInfoEntry(PlatformDependentEmbeddedFileWriter* w,
uint64_t rva_start, uint64_t rva_end) const;
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define V8_COMPILER_IS_MSVC
#endif
#if defined(V8_COMPILER_IS_MSVC)
// Windows MASM doesn't have an .octa directive, use QWORDs instead.
// Note: MASM *really* does not like large data streams. It takes over 5
// minutes to assemble the ~350K lines of embedded.S produced when using
// BYTE directives in a debug build. QWORD produces roughly 120KLOC and
// reduces assembly time to ~40 seconds. Still terrible, but much better
// than before. See also: https://crbug.com/v8/8475.
static constexpr DataDirective kByteChunkDirective = kQuad;
static constexpr int kByteChunkSize = 8;
static int WriteByteChunk(PlatformDependentEmbeddedFileWriter* w,
int current_line_length, const uint8_t* data) {
const uint64_t* quad_ptr = reinterpret_cast<const uint64_t*>(data);
return current_line_length + w->HexLiteral(*quad_ptr);
}
#elif defined(V8_OS_AIX)
// PPC uses a fixed 4 byte instruction set, using .long
// to prevent any unnecessary padding.
static constexpr DataDirective kByteChunkDirective = kLong;
static constexpr int kByteChunkSize = 4;
static int WriteByteChunk(PlatformDependentEmbeddedFileWriter* w,
int current_line_length, const uint8_t* data) {
const uint32_t* long_ptr = reinterpret_cast<const uint32_t*>(data);
return current_line_length + w->HexLiteral(*long_ptr);
}
#else // defined(V8_COMPILER_IS_MSVC) || defined(V8_OS_AIX)
static constexpr DataDirective kByteChunkDirective = kOcta;
static constexpr int kByteChunkSize = 16;
static int WriteByteChunk(PlatformDependentEmbeddedFileWriter* w,
int current_line_length, const uint8_t* data) {
const size_t size = kInt64Size;
uint64_t part1, part2;
// Use memcpy for the reads since {data} is not guaranteed to be aligned.
#ifdef V8_TARGET_BIG_ENDIAN
memcpy(&part1, data, size);
memcpy(&part2, data + size, size);
#else
memcpy(&part1, data + size, size);
memcpy(&part2, data, size);
#endif // V8_TARGET_BIG_ENDIAN
if (part1 != 0) {
current_line_length +=
fprintf(w->fp(), "0x%" PRIx64 "%016" PRIx64, part1, part2);
} else {
current_line_length += fprintf(w->fp(), "0x%" PRIx64, part2);
}
return current_line_length;
}
#endif // defined(V8_COMPILER_IS_MSVC) || defined(V8_OS_AIX)
#undef V8_COMPILER_IS_MSVC
static int WriteDirectiveOrSeparator(PlatformDependentEmbeddedFileWriter* w,
int current_line_length,
DataDirective directive) {
int printed_chars;
if (current_line_length == 0) {
printed_chars = w->IndentedDataDirective(directive);
DCHECK_LT(0, printed_chars);
} else {
printed_chars = fprintf(w->fp(), ",");
DCHECK_EQ(1, printed_chars);
}
return current_line_length + printed_chars;
}
static int WriteLineEndIfNeeded(PlatformDependentEmbeddedFileWriter* w,
int current_line_length, int write_size) {
static const int kTextWidth = 100;
// Check if adding ',0xFF...FF\n"' would force a line wrap. This doesn't use
// the actual size of the string to be written to determine this so it's
// more conservative than strictly needed.
if (current_line_length + strlen(",0x") + write_size * 2 > kTextWidth) {
fprintf(w->fp(), "\n");
return 0;
} else {
return current_line_length;
}
}
static void WriteBinaryContentsAsInlineAssembly(
PlatformDependentEmbeddedFileWriter* w, const uint8_t* data,
uint32_t size) {
int current_line_length = 0;
uint32_t i = 0;
// Begin by writing out byte chunks.
for (; i + kByteChunkSize < size; i += kByteChunkSize) {
current_line_length = WriteDirectiveOrSeparator(w, current_line_length,
kByteChunkDirective);
current_line_length = WriteByteChunk(w, current_line_length, data + i);
current_line_length =
WriteLineEndIfNeeded(w, current_line_length, kByteChunkSize);
}
if (current_line_length != 0) w->Newline();
current_line_length = 0;
// Write any trailing bytes one-by-one.
for (; i < size; i++) {
current_line_length =
WriteDirectiveOrSeparator(w, current_line_length, kByte);
current_line_length += w->HexLiteral(data[i]);
current_line_length = WriteLineEndIfNeeded(w, current_line_length, 1);
}
if (current_line_length != 0) w->Newline();
}
static int ExternalFilenameIndexToId(int index) {
return kFirstExternalFilenameId + index;
}
static int ExternalFilenameIdToIndex(int id) {
return id - kFirstExternalFilenameId;
}
std::vector<byte> source_positions_[Builtins::builtin_count];
#if defined(V8_OS_WIN_X64)
win64_unwindinfo::BuiltinUnwindInfo unwind_infos_[Builtins::builtin_count];
#endif
// In assembly directives, filename ids need to begin with 1.
static const int kFirstExternalFilenameId = 1;
std::map<const char*, int> external_filenames_;
std::vector<const char*> external_filenames_by_index_;
const char* embedded_src_path_ = nullptr;
const char* embedded_variant_ = kDefaultEmbeddedVariant;
};
} // namespace internal
} // namespace v8
#endif // V8_SNAPSHOT_EMBEDDED_FILE_WRITER_H_