mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 06:46:05 +00:00

Currently, WebAssembly/WASI target does not provide direct support for code coverage. This patch set fixes several issues to unlock the feature. The main changes are: 1. Port `compiler-rt/lib/profile` to WebAssembly/WASI. 2. Adjust profile metadata sections for Wasm object file format. - [CodeGen] Emit `__llvm_covmap` and `__llvm_covfun` as custom sections instead of data segments. - [lld] Align the interval space of custom sections at link time. - [llvm-cov] Copy misaligned custom section data if the start address is not aligned. - [llvm-cov] Read `__llvm_prf_names` from data segments 3. [clang] Link with profile runtime libraries if requested See each commit message for more details and rationale. This is part of the effort to add code coverage support in Wasm target of Swift toolchain.
287 lines
8.5 KiB
C++
287 lines
8.5 KiB
C++
//===- OutputSections.cpp -------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "OutputSections.h"
|
|
#include "InputChunks.h"
|
|
#include "InputElement.h"
|
|
#include "InputFiles.h"
|
|
#include "OutputSegment.h"
|
|
#include "WriterUtils.h"
|
|
#include "lld/Common/ErrorHandler.h"
|
|
#include "lld/Common/Memory.h"
|
|
#include "llvm/ADT/Twine.h"
|
|
#include "llvm/Support/LEB128.h"
|
|
#include "llvm/Support/Parallel.h"
|
|
|
|
#define DEBUG_TYPE "lld"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::wasm;
|
|
|
|
namespace lld {
|
|
|
|
// Returns a string, e.g. "FUNCTION(.text)".
|
|
std::string toString(const wasm::OutputSection &sec) {
|
|
if (!sec.name.empty())
|
|
return (sec.getSectionName() + "(" + sec.name + ")").str();
|
|
return std::string(sec.getSectionName());
|
|
}
|
|
|
|
namespace wasm {
|
|
StringRef OutputSection::getSectionName() const {
|
|
return sectionTypeToString(type);
|
|
}
|
|
|
|
void OutputSection::createHeader(size_t bodySize) {
|
|
raw_string_ostream os(header);
|
|
debugWrite(os.tell(), "section type [" + getSectionName() + "]");
|
|
encodeULEB128(type, os);
|
|
writeUleb128(os, bodySize, "section size");
|
|
log("createHeader: " + toString(*this) + " body=" + Twine(bodySize) +
|
|
" total=" + Twine(getSize()));
|
|
}
|
|
|
|
void CodeSection::finalizeContents() {
|
|
raw_string_ostream os(codeSectionHeader);
|
|
writeUleb128(os, functions.size(), "function count");
|
|
bodySize = codeSectionHeader.size();
|
|
|
|
for (InputFunction *func : functions) {
|
|
func->outputSec = this;
|
|
func->outSecOff = bodySize;
|
|
func->calculateSize();
|
|
// All functions should have a non-empty body at this point
|
|
assert(func->getSize());
|
|
bodySize += func->getSize();
|
|
}
|
|
|
|
createHeader(bodySize);
|
|
}
|
|
|
|
void CodeSection::writeTo(uint8_t *buf) {
|
|
log("writing " + toString(*this) + " offset=" + Twine(offset) +
|
|
" size=" + Twine(getSize()));
|
|
log(" headersize=" + Twine(header.size()));
|
|
log(" codeheadersize=" + Twine(codeSectionHeader.size()));
|
|
buf += offset;
|
|
|
|
// Write section header
|
|
memcpy(buf, header.data(), header.size());
|
|
buf += header.size();
|
|
|
|
// Write code section headers
|
|
memcpy(buf, codeSectionHeader.data(), codeSectionHeader.size());
|
|
|
|
// Write code section bodies
|
|
for (const InputChunk *chunk : functions)
|
|
chunk->writeTo(buf);
|
|
}
|
|
|
|
uint32_t CodeSection::getNumRelocations() const {
|
|
uint32_t count = 0;
|
|
for (const InputChunk *func : functions)
|
|
count += func->getNumRelocations();
|
|
return count;
|
|
}
|
|
|
|
void CodeSection::writeRelocations(raw_ostream &os) const {
|
|
for (const InputChunk *c : functions)
|
|
c->writeRelocations(os);
|
|
}
|
|
|
|
void DataSection::finalizeContents() {
|
|
raw_string_ostream os(dataSectionHeader);
|
|
unsigned segmentCount = llvm::count_if(segments, [](OutputSegment *segment) {
|
|
return segment->requiredInBinary();
|
|
});
|
|
#ifndef NDEBUG
|
|
unsigned activeCount = llvm::count_if(segments, [](OutputSegment *segment) {
|
|
return (segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE) == 0;
|
|
});
|
|
#endif
|
|
|
|
assert((config->sharedMemory || !ctx.isPic || config->extendedConst ||
|
|
activeCount <= 1) &&
|
|
"output segments should have been combined by now");
|
|
|
|
writeUleb128(os, segmentCount, "data segment count");
|
|
bodySize = dataSectionHeader.size();
|
|
bool is64 = config->is64.value_or(false);
|
|
|
|
for (OutputSegment *segment : segments) {
|
|
if (!segment->requiredInBinary())
|
|
continue;
|
|
raw_string_ostream os(segment->header);
|
|
writeUleb128(os, segment->initFlags, "init flags");
|
|
if (segment->initFlags & WASM_DATA_SEGMENT_HAS_MEMINDEX)
|
|
writeUleb128(os, 0, "memory index");
|
|
if ((segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE) == 0) {
|
|
if (ctx.isPic && config->extendedConst) {
|
|
writeU8(os, WASM_OPCODE_GLOBAL_GET, "global get");
|
|
writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(),
|
|
"literal (global index)");
|
|
if (segment->startVA) {
|
|
writePtrConst(os, segment->startVA, is64, "offset");
|
|
writeU8(os, is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD, "add");
|
|
}
|
|
writeU8(os, WASM_OPCODE_END, "opcode:end");
|
|
} else {
|
|
WasmInitExpr initExpr;
|
|
initExpr.Extended = false;
|
|
if (ctx.isPic) {
|
|
assert(segment->startVA == 0);
|
|
initExpr.Inst.Opcode = WASM_OPCODE_GLOBAL_GET;
|
|
initExpr.Inst.Value.Global = WasmSym::memoryBase->getGlobalIndex();
|
|
} else {
|
|
initExpr = intConst(segment->startVA, is64);
|
|
}
|
|
writeInitExpr(os, initExpr);
|
|
}
|
|
}
|
|
writeUleb128(os, segment->size, "segment size");
|
|
|
|
segment->sectionOffset = bodySize;
|
|
bodySize += segment->header.size() + segment->size;
|
|
log("Data segment: size=" + Twine(segment->size) + ", startVA=" +
|
|
Twine::utohexstr(segment->startVA) + ", name=" + segment->name);
|
|
|
|
for (InputChunk *inputSeg : segment->inputSegments) {
|
|
inputSeg->outputSec = this;
|
|
inputSeg->outSecOff = segment->sectionOffset + segment->header.size() +
|
|
inputSeg->outputSegmentOffset;
|
|
}
|
|
}
|
|
|
|
createHeader(bodySize);
|
|
}
|
|
|
|
void DataSection::writeTo(uint8_t *buf) {
|
|
log("writing " + toString(*this) + " offset=" + Twine(offset) +
|
|
" size=" + Twine(getSize()) + " body=" + Twine(bodySize));
|
|
buf += offset;
|
|
|
|
// Write section header
|
|
memcpy(buf, header.data(), header.size());
|
|
buf += header.size();
|
|
|
|
// Write data section headers
|
|
memcpy(buf, dataSectionHeader.data(), dataSectionHeader.size());
|
|
|
|
for (const OutputSegment *segment : segments) {
|
|
if (!segment->requiredInBinary())
|
|
continue;
|
|
// Write data segment header
|
|
uint8_t *segStart = buf + segment->sectionOffset;
|
|
memcpy(segStart, segment->header.data(), segment->header.size());
|
|
|
|
// Write segment data payload
|
|
for (const InputChunk *chunk : segment->inputSegments)
|
|
chunk->writeTo(buf);
|
|
}
|
|
}
|
|
|
|
uint32_t DataSection::getNumRelocations() const {
|
|
uint32_t count = 0;
|
|
for (const OutputSegment *seg : segments)
|
|
for (const InputChunk *inputSeg : seg->inputSegments)
|
|
count += inputSeg->getNumRelocations();
|
|
return count;
|
|
}
|
|
|
|
void DataSection::writeRelocations(raw_ostream &os) const {
|
|
for (const OutputSegment *seg : segments)
|
|
for (const InputChunk *c : seg->inputSegments)
|
|
c->writeRelocations(os);
|
|
}
|
|
|
|
bool DataSection::isNeeded() const {
|
|
for (const OutputSegment *seg : segments)
|
|
if (seg->requiredInBinary())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// Lots of duplication here with OutputSegment::finalizeInputSegments
|
|
void CustomSection::finalizeInputSections() {
|
|
SyntheticMergedChunk *mergedSection = nullptr;
|
|
std::vector<InputChunk *> newSections;
|
|
|
|
for (InputChunk *s : inputSections) {
|
|
s->outputSec = this;
|
|
MergeInputChunk *ms = dyn_cast<MergeInputChunk>(s);
|
|
if (!ms) {
|
|
newSections.push_back(s);
|
|
continue;
|
|
}
|
|
|
|
if (!mergedSection) {
|
|
mergedSection =
|
|
make<SyntheticMergedChunk>(name, 0, WASM_SEG_FLAG_STRINGS);
|
|
newSections.push_back(mergedSection);
|
|
mergedSection->outputSec = this;
|
|
}
|
|
mergedSection->addMergeChunk(ms);
|
|
}
|
|
|
|
if (!mergedSection)
|
|
return;
|
|
|
|
mergedSection->finalizeContents();
|
|
inputSections = newSections;
|
|
}
|
|
|
|
void CustomSection::finalizeContents() {
|
|
finalizeInputSections();
|
|
|
|
raw_string_ostream os(nameData);
|
|
encodeULEB128(name.size(), os);
|
|
os << name;
|
|
|
|
for (InputChunk *section : inputSections) {
|
|
assert(!section->discarded);
|
|
payloadSize = alignTo(payloadSize, section->alignment);
|
|
section->outSecOff = payloadSize;
|
|
payloadSize += section->getSize();
|
|
}
|
|
|
|
createHeader(payloadSize + nameData.size());
|
|
}
|
|
|
|
void CustomSection::writeTo(uint8_t *buf) {
|
|
log("writing " + toString(*this) + " offset=" + Twine(offset) +
|
|
" size=" + Twine(getSize()) + " chunks=" + Twine(inputSections.size()));
|
|
|
|
assert(offset);
|
|
buf += offset;
|
|
|
|
// Write section header
|
|
memcpy(buf, header.data(), header.size());
|
|
buf += header.size();
|
|
memcpy(buf, nameData.data(), nameData.size());
|
|
buf += nameData.size();
|
|
|
|
// Write custom sections payload
|
|
for (const InputChunk *section : inputSections)
|
|
section->writeTo(buf);
|
|
}
|
|
|
|
uint32_t CustomSection::getNumRelocations() const {
|
|
uint32_t count = 0;
|
|
for (const InputChunk *inputSect : inputSections)
|
|
count += inputSect->getNumRelocations();
|
|
return count;
|
|
}
|
|
|
|
void CustomSection::writeRelocations(raw_ostream &os) const {
|
|
for (const InputChunk *s : inputSections)
|
|
s->writeRelocations(os);
|
|
}
|
|
|
|
} // namespace wasm
|
|
} // namespace lld
|